What’s wrong with this code? A bit of background for this example; `driver` is a Selenium::WebDriver object, the Selenium server is remote. Also, this technique is applicable to all expensive code that does the same thing over and over.
1 2 3 4 5 6 7 8 9 10 11
def first_name_field (driver.find_elements:id, "name") end
unless first_name_field.nil? if first_name_field.displayed? if first_name_field.enabled?
first_name_field.send_keys"Dylan" end end end
Apart from being terribly contrived, this code is bad because every single interaction with first_name_field causes a call to find_elements, which in turn reaches out to the Selenium server. You’re adding a network round trip every single time, and for no reason. Instead of asking the browser to do this:
Find the “name” element (line 5)
Check if the name element is displayed (line 6)
Check if the name element is enabled (line 7)
Send the string “Dylan” to the name element (line 8)
You’re asking it to do this:
Find the “name” element (line 5)
Find the “name” element (line 6)
Check if the name element is displayed (line 6)
Find the “name” element (line 7)
Check if the name element is enabled (line 7)
Find the “name” element (line 8)
Send the string “Dylan” to the name element (line 8)
This doesn’t just occur through Selenium commands (although Page Object Model code and libraries are often prone working this way). Loading configurations, accessing web services, validating data. There’s many places where you’ll burn up cycles (and correspondingly incinerate coal and warm the planet dooming us all just a liiiiitle sooner. Yeah, I actually do think wasted compute matters.) and, maybe more sellable to your Product Owner (who clearly doesn’t care about the environment), this will make your code faster.
Are you writing this down, and taking a memo?
No, like, literally. Take a memo. This kind of memo. Memoization is where you save the result of an expensive operation and return the saved result every time the operation is requested. Like responding to everything your Mum says on the phone with “Uhuh”, Memoization swaps one resource (computing time or brain-cells) for another (memory or engagement). Memory is cheap. You can probably throw more into your server if you need it. (But you don’t need it. Just… You don’t, trust me.)
If you use Ruby, you’re probably already taking advantage of Memoization:
def proscuitto_pizza @proscuitto_pizza||= Pizza.new(:delicious) end
That’s right, the ||= operator in Ruby effectively does Memoization; If the variable exists, it does nothing, else it sets it to the code on on the right of the operator. Wrap the whole lot up in a method name and you’ve got a memoized value.
For example, we could re-write the code above as so: def first_name_field @first_name_field||= (driver.find_elements:id, "name") end
That single line change means that Ruby will remember the field in question and we won’t need to ask the Selenium server for it again, saving us a heap of time.
(Selenium Sidenote – Unless the page changes of course. How do you know if the page changes? Well, every navigation will do it (including those involving submitting forms) and some AJAX requests do it. You need to understand how your site is implemented… But then, you should anyway so you know what you’re actually testing).
Entirely beside the point of whether you consider Lattes to have value, I assume you consider your career does? Maybe you’re working up to a glorious technocratic empire, constant spreads in Inc., Johnny Ive begging you to please, please come back to Apple. Perhaps you’d just like to not go bankrupt.
Avdi Grimm, Generally Awesome Person and Excellent Ruby Developer, would also like this. He posted this article explaining that, while he’s not in imminent danger of dying in debtors prison, he’s not liking his income trend.
This was probably a rough thing to admit because talking about money makes people weird and evidence of failure is often taken as evidence of deserved failure. To me it was rough to read because Avdi is an excellent developer who makes fantastic resources. I’ve loved reading Confident Ruby. I’ve been a subscriber to Ruby Tapas (his short-form screencast series) for over two years. If you’re wanting to invest some cash in your career, and have that go directly to a literal Ruby Hero, go read the blog post and then buy a book or subscribe to Ruby Tapas. You’ll get value out of them. And yes, I think they’re good for a developer’s work as at least two cups of coffee.
(I’m not being paid for this post; I don’t think I’ve ever met Avdi, I just think he’s made some nifty stuff and this was a good chance to turn you all onto it.)
I generally don’t make New Years Resolutions. The entire process is so arbitrary and self-loathing, it plays into the whole annus horibilis bollucks. If you’re going to improve your life, just do so; Don’t wait for some arbitrary point to go this is the year I will be self fulfilled, all my dreams will approach realization, I will open my code chakras and purge my stale git stashes and finally learn how the hell to use Vim. If you’re constantly trying to learn new things you’re going to be better off then it you wait to be pushed by the last two digits of the date++.
At least pick more then one point in the year to fix ALL The Things. One thing a month. You’ll get continuous benefits for your life and won’t be exhausted from trying to change everything all at once. You don’t want to go through all the stress of making a change, just to suffer from a fit of Extinction Bursts all at once. Failing at multiple goals at once can lead to feeling like a children’s toy that’s been run over, by a leaking sewerage truck, in the rain: Useless, disgusting and prone to making gross squelching sounds.
However. It’s excellent blog credit to give a list of your New Years resolutions; Gotta get those views, that’s how you get that Internet Monies from the Internet People (Or so I hear). So instead of giving my resolutions, I’m going to make some suggestions that everyone else could adopt. Please feel free to give me all the credit for these, despite the willpower being expended by you and you alone. There’s going to be twelve in total, one for each month. Here are the first three, and I’ll publish the rest every quarter, just to kill off the temptation to start them all at once.
Improvement One: Learn a Tech
Learning a new technology has an obvious benefit: It makes you more employable. Personally I think this is the least beneficial aspect. Learning a new tech, especially one that is wildly different from what you do day-to-day, makes you better at your current job.
It’s been solid advice for a while, that learning a different programming paradigm makes your code better in all languages. I don’t actually buy this. “writes Java in Ruby” is the same as “acts Functionally in JS“; they’re both paradigm contamination. I think that practise makes you better, but that’s a very different thing.
No, the reason that I think learning a tech makes you better at your job is it lets you talk to other developers in their language, shows you the strengths of technologies that might make your own specialty more useful, and allows you to better boot-strap individual projects. A shinier prototype is more likely to be turned into a Real Project.
I personally really like Code School for learning new tech. I gave up on books because it’s difficult to know what’s worth reading and what isn’t, until you’ve enough experience to evaluate the content and, well… that experience is what you’re reading to build. I like Code School because it treats the viewer like an adult (unlike at least one competitor) and because it builds in logical, sensible ways. I’m not getting paid for this recommendation (although if Code School wants… call me!), I just like the product.
Improvement Two: Take a Step to Better Financial Management
Capitalism is pretty gross. It’s also widely entrenched in society and unlikely to be replaced by anything better before the AI post-scarcity uprising, and I hear that project didn’t meet its KickStarter goal. Money is here to stay, and Money roughly correlates with ability. Not ability to accomplish tasks; ability to get others to help (or at least get out of the way).
Spare funds give you the ability to quit and find a better job. Spare funds allow you to buy tools for your hobby and improve your mental health. Spare funds let you travel. Spare funds let you start a side project, that gets profitable, that makes you more spare funds. It’s kind of crap that having money makes it easier to make money. It makes life less stressful. It literally makes you less sad and maybe happier up to a point (The literature is mixed. This is a nice summary. TL;DR Money gives you options. Wow, what a familiar argument…).
This is not financial advice. I do not know what your goals are… But maybe you’re lucky enough that with focus and willpower, you can put yourself in a place with more ability to achieve them. Maybe start with an Emergency Fund?
Improvement Three: Be a Part of your Tech Community (more)
This one is pretty easy: Get more involved in a tech community. The cold hard reason is that it’ll serve as good networking. Screw that. Tech is (or should be) fun. Tech people are some of my favourite kinds of people. There are meetups for languages and tools and industries all over the world. Go to a conference (like RubyConf.au!) and deliberately talk to people you don’t know. Head to your local meetup. Join and post to some Open Source Mailing lists.
If you’re already involved, step it up. Pitch talks to conferences and meetups. Organize an event. Go to a longer event or workshop (Rails Camps are amazing). Mentoring is a huge benefit to our industry, so find a junior dev and offer. Help less-connected people get integrated by going with them to events and introducing them to others.
Especially try to find unique and interesting voices and share them with others. We can be faddy, prone to hero worship and appealing to authority. Mix it up! Share things you don’t often here, from people who aren’t often heard. Everyone gets better that way.
Postscript: Sorry to Preach
I really really can’t stand New Years Resolutions, which is why this post is deliberately delayed. All of these are suggestions of things that I’d like to accomplish myself, and may be helpful in your life. They’re not aimed at anyone in particular, nor do I warrant their practicality or easy. Any feedback, leave a comment below!
Something that will, sadly, not be a secret to many of you: Some people are awful.
Even if you think it’s acceptable to be a rude arrogant jerk to people in private, (and would you want, as queer person, to work with someone you know donates heavily to prevent marriage equality, even ‘in private’?) it’s very hard to argue that it’s OK in professional life. Open Source is no longer just a fun hobby; it’s a professional reality that you will use and (hopefully) contribute to Open Source. Unfortunately there are people in many many projects, often in significant roles, that will say things that are cruel, hurtful, that erase and trivialise people’s identities and personal triumphs and disadvantages. They’re being bullies ‘at work’. That’s super NOT OK.
One of the best ways to make communities professional and welcoming to beginners, as well as enhancing diversity, is to adopt and enforce a Code of Conduct. Ashe Dryden answers all of the questions you might have about why and how far better then I could, but I want to re-enforce five things:
Having a (good) Code of Conduct makes your project more welcoming to everyone willing to not be a prat. Diversity is at the heart of the Internet
Code of Conduct violations are not about being offended, they are about being unprofessional
You may have a right to freedom of speech but there’s no right to freedom of consequences
If there’s ‘no problem’, then there’s no harm in adopting a Code of Conduct! (Spoiler: there totally is a problem)
And number five:
objection.text = "but you're discriminating against my right to discriminate!"
rescue RediculousCircularObjectionException => e
objection.objector.rebutt "Really? Really. You think that's a coherent argument?"
You know who’s not awful? Coraline Ada Ehmke. She’s actually really great. She’s proposed a Code of Conduct for Ruby Core (and wrote the original Contributor Covenant) and you can find that proposal here. All y’all with Core Ruby accounts should go support it.
What do we have to lose? Awful people, that’s what. HUZZAH!
In Japan, things are a little more advanced. We know they’ve already got flying lazer cars and evil death robots who battle special teenagers with super transformation sequences, but they even have more boozing. See, the age of majority is lower then in the United States – it’s 20. In Japan, you get a whole year of extra boozin’ over your counterparts (although Australia is still two years ahead again).
Why am I bringing this up besides skyting at my friends from Freedomland? Well, because today, my favorite programming language, Ruby, can go out, smoke an entire pack of Lights, grope a paid adult entertainer, down an entire bottle of whiskey and then loudly throw up all over the street, because it turns Twenty today.
Now, I know you aren’t invited to the party (Because you’re not as cool as me and Ruby’s Daddy Matz doesn’t want your sort around) but Ruby is going HARD. It’s already updated it’s official age on its profile! OK, so there’s a decimal point in the wrong place but like YOU could write your age correctly after getting shitfaced on 2 bottles of sake and that really weird beer your sketchy friend drinks. Frankly, as long as Ruby doesn’t wake up next to Oracle, covered only in honey and fake fur, I think it’s doing pretty well.
Happy Birthday Ruby, Thanks Matz, and Rock on, Ruby Community! (Check out the SWEET gifts Ruby got At Ben Hoskings’ Blog)
(TL;DR for boring people –> Ruby version 2.0 is out AND it’s the 20th birthday of Ruby. Install, Code, Win.)
So I worked all weekend this week. I won’t get paid. I won’t even get a nod from my boss, although I was there for even longer for a standard work day. Jerks. Time to burn the office down. Get my stapler back.
The idea is that you spend a day focusing on implementing the same thing over and over, so you can hone your professional skills. In the same way as a sculptor might practice straight lines over and over, we implemented Conway’s Game of Life using TDD techniques in 45 minutes. Every 45 minutes you had to delete your code and tests, debrief, and start again with a new constraint. Some of the constraints felt more annoying then educational, but on the whole I felt I got something out of every session. I also got to teach a couple of people something about TDD & RSpec, and I love helping others improve, so I got a kick out of being able to contribute like that.
I’m going to post how I found each session today, then mull over the others and post some observations a little bit later. I think it’s best to see how learning effects you in other contexts (like actually at work) before you jump into critiquing it, so I’m going to see how the next work week goes.
Language: Ruby Testing Framework: RSpec
This session just booted people off into implementing Conway’s life. I worked with a young Thoughtworker whose name eluded me, and we got about halfway into an implementation before time was up.
We spent the majority of this session just discussing what way to do things. I was a big fan of using a “Cell” object which maintained a list of its own neighbours, without directional information because it doesn’t matter. Doing it like this allows for arbitrary topologies (And I suspected that might be one of the constraints.)
Potentially tricky issues with this approach include implementing the tick for every cell at once(If you duplicate the cells, you have to duplicate the network and ensure you’ve duplicated every cell with its new state. If you mutate the network in place, each cell needs to know if it’s neighbours have ticked, and if so, what their state was last tick.
At the end of the session we had a discussion about what techniques people tried. Having a “Cell” object was quite popular (OO is like herpes, it spreads with contact), there was one “have a universe holding an array of cells” solution, and the one which I liked most, which was using the co-ordinates of the cells as a key for a hash of booleans, representing whether the cell is alive or dead. I really liked this… It’s unbounded (unlike an array in many languages) and *doesn’t* require a hojillion objects.
Session 2 – Ping Pong
Language: Ruby Testing Framework: RSpec Constraint: Each time you write a test, you ensure it tests properly, then you pass it to your pair and they have to do all the implementation. Then they write the next test, and you implement that
This session we decided to try the hash technique one of the groups suggested last time. I say”we decided” but actually mean “I decided”, because prevarication frustrates me, my partner had no strong opinions and I thought it sounded interesting so I made a snap decision.
The Ping Pong criteria is how I’d probably prefer to do pairing in the future, although usually it irks me (I need to be doing something and my “I’m not doing anything” behaviors usually irritate my pair). It meant that your tests had to be higher quality, and there was a defined place where you had to hand over to the other member of your pair — No long stints at the keyboard or just watching.
We didn’t get an implementation finished though, although I worked out how I’d like to derive neighbours. I proceeded to teach people a tiny bit of matrix math all day (more on that later)
Session 3 – Silent Ping Pong
Language: Ruby Testing Framework: RSpec Constraint: The same Ping Pong TDD as before, but this time you’re not allowed to talk
I paired again with a Thoughtworker this time, a gent from China called Hiyun (I hope, sorry if I’m wrong mate >.>). Wow. Dude was a shortcut pro. I learned a shittonne of RubyMine shortcuts and tricks I’d always wanted too but never devoted time to (Which is obviously my fault). It was really cool, probably my favorite session of the day.
We went back to the “Cell as self aware object” paradigm (drink). With the exception of a couple of inadequate tests, this session worked remarkably well. The inadequate tests required a lot of gesticulating. Once you’ve written something it’s hard to prove that the test is wrong rather then the code.
I think not being able to talk focused our attentions on communicating *only* our requirements through tests. There was no discussions of where you worked, what you thought of Test::Unit VS RSpec, why’s DHH’s hair like that; it was purely a “get shit done” event.
I think the most surprising thing was that we finished. It was almost like being unable to design architecture by consensus made one arise naturally out of requirements, which I am… skeptical of. I’m not sure how well our solution would have accommodated change, but maybe most software projects are simple enough that it doesn’t matter.
Session Four – No Primatives
Language: Ruby Testing Framework: RSpec Constraint: Rather then using native types to store results, cells and so on, use a wrapper class or object. No Arrays of cells, no Object.live? returns true
Oh god so hard. Getting away from primitives in Ruby is hard. Every time I wanted to wrap something I heard a tiny voice in my head stop talking about bees, games, cooking and Alice in Wonderland for a minute to scream “NEEdleSS ComPleXItY!“. All I wanted to do was use built in objects so I could rely on methods that Someone Else Has Tested™.
This session, we did something horrific. We decided that, if we weren’t going to use primitives, we’d use something as far removed as possible. Something so non-primative it’d make your eyes haemorrhage. Files.
Yup. Files. To check if there was a cell at (4,5), you checked the directory ./data/4 for file 5.cell. If it existed, it was alive. Touch a location to make a cell alive, rm to delete it. We were planning to store the current generation number in the file to allow for ticking.
How’s that for non-primitives, bitches?
Session Five – Tests must pass in 3 minutes
Language: Java Testing Framework: JUnit Constraint: Once you start writing a test, you must write code to make it fail in three minutes or less. If you get to three minutes without it passing, you must revert all you changes and delete your test
The intent of this constraint, I believe, was to force you to test small amounts of functionality rather then huge sweeping changes. The idea is that small tests are good tests, and it’s tempting to write large tests that try to do too much. When they fail, you’re not actually sure why, and you lose much of the advantage of a full test suite.
My pair (Hi, Mike!) and I chose Java because it was our lingua franca (Scala was preferred but it’s quite hard to use a language without a dev environment, oddly enough). This may not have been the wisest choice, because of one simple truism:
Fuck me Java is Verbose!
It’s not just the punchline to the “what’s wrong with other people’s languages” joke. It was never really bought home to me just how verbose Java was until I had to spend so much time setting up each test. I’d just spent most of the day writing Ruby and now I’m having to give all my tests annotations and a typed, static method signature, and set up my variables types, and then set up and type the method and ensure to return the right sort of thing, and just making so much boilerplate it nearly made my eyes bleed (I propose we call this Stigjava.)
Even Mike, who writes Java for a living, was surprised. He said it bought Java’s verbosity into stark relief after a day of writing Ruby and Haskell. It forced us into doing true TDD because otherwise we simply couldn’t make a test that tested anythingin time. We had to implement the minimal amount every single test pass. Here’s how we had to test that we could get a list of neighbours for a cell:
Write a test to ensure that there is a “getNeighbours” method. Just that one exists. Pass it
Write a test to ensure the method returns an (empty) ArrayList. Pass it.
Write a test to ensure the method returns an ArrayList containing eight things
Run out of time because it took so long to learn how much code we could write at once
It was just astonishing. I’m not Java bashing (so passé), just really surprised. Writing the code to pass things, and even the tests, wash’t too hard, just getting to a point where we could do something took forever. And Mike is an IntelliJ wizard – Otherwise we’d have been screwed. He was making sweet combo love to the keyboard to bust out methods and variables and implement the signatures correctly and we were still fighting the clock.
Session Six – No Returns
Language: Ruby Testing Framework: RSpec Constraint: None of our methods could return values.
Woah. OK. Globalling it up. During this session I had a bit of an idea how to use events to generate a grid of neighbors, but didn’t implement it cleanly. Actually, this session was embarrassing because I forgot that Ruby is pass-by-reference, so resorted to using Globals. Don’t tell my mother. The solution wasn’t really productive, just because every time I manipulated the global objects I felt dirty and wanted to cry.
During this session, the pair using Haskell gave up on the constraint and just started trying to get it working at all. Globals were popular (at least we weren’t alone wallowing in filth) and only one group used callbacks. The idea I’d had was callback/eventing based, but wasn’t async, so obviously I need to do a bit more work about doing that kind of programming at need, rather then when forced.
Then, we had beer, a debrief and a powwow. Roughly half the room left straight away and the rest wanted to hang for a bit, which I always enjoy. I’m looking forward to the next one. I’m thinking this time, we use the presence of machines on a network as live cells….
I’m using Savon to deal with a SOAP webservice I’m forced to interact with against my will. After a few weirdo caveats (Namespacing) that were more the target service’s fault (Who the fuck changes from Camel_Case to lower_Camel_Case at will for attribute names? Fuckwits, that’s who) it’s been a mostly pleasant ride.
Until I upgraded to Jruby 1.7.0-Preview1 and the autocompletion code which backed onto the service started failing horribly. Specifically, empty strings were being presented (Which resulted in partly constructed results like “75 Rd, Westbank“). So I dug into my logs and found out that I wasn’t getting strings, I was getting Nori::StringWithAttribute. A further dig discovered that there’s a defect in psych, the standard YAML library, which means that it doesn’t deserialize subclasses of string properly.
Before you rush out to get your grubby mits all over a core library patch submission, it’s been fixed in Ruby master. Just not Jruby master. Which is a shame, because that’s what I’m using, and I don’t have a build environment set up to develop for JRuby (Or the time, right now). I also don’t have the ability to use psych by itself, because it’s a native library and I’m on Windows.
You may have noticed, sometime in the last few days, that the fantastic Ruby IDE RubyMine from those lovable Scamps JetBrains, has been having a small problem. Nothing too shameful or embarassing but well, it wants to keep it to its doctor and partner.
Oh Well, this is the internet. Medical Disclosure AHOY!
Instead of getting useful output, RubyMine is suffering an exception discharge from its testhole. It looks much like this:
ArgumentError: wrong number of arguments (0for1)
run_suite at C:/Program Files (x86)/JetBrains/RubyMine 4.0.3/rb/testing/patch/testunit/test/unit/ui/testrunnermediator.rb:41
start_mediator at C:/Program Files (x86)/JetBrains/RubyMine 4.0.3/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:131
start at C:/Program Files (x86)/JetBrains/RubyMine 4.0.3/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:119
run at C:/jruby/jruby-220.127.116.11/lib/ruby/gems/1.8/gems/test-unit-2.5.1/lib/test/unit/ui/testrunnerutilities.rb:24
run at C:/jruby/jruby-18.104.22.168/lib/ruby/gems/1.8/gems/test-unit-2.5.1/lib/test/unit/autorunner.rb:378
change_work_directory at C:/jruby/jruby-22.214.171.124/lib/ruby/gems/1.8/gems/test-unit-2.5.1/lib/test/unit/autorunner.rb:434
run at C:/jruby/jruby-126.96.36.199/lib/ruby/gems/1.8/gems/test-unit-2.5.1/lib/test/unit/autorunner.rb:377
run at C:/jruby/jruby-188.8.131.52/lib/ruby/gems/1.8/gems/test-unit-2.5.1/lib/test/unit/autorunner.rb:58 (root) at C:/jruby/jruby-184.108.40.206/lib/ruby/gems/1.8/gems/test-unit-2.5.1/lib/test/unit.rb:330
Process finished with exit code 1
Empty test suite.
Well OK. That’s… That’s not good. You might want to get that seen too, RubyMine. Let’s run some scans and take a swab of those lines.
# Notify test reporter attached
# Notify patched size
notify_listeners(TC_TESTCOUNT, count) # delegate call to super
Well something’s being aliased to old_run_suite, and it’s in Test::Unit::UI::TestRunnerMediator. Let’s check that out: test-unit-2.5.1\lib\test\unit\ui\testrunnermediator.rb
92 93 94 95 96
def run_suite(result) @suite.run(result)do|channel, value|
notify_listeners(channel, value) end end
Huh. That’s a non-optional argument alright. Wait how could that have ever worked? As far as I can tell this should always have resulted in chunks being blown all over my development environment, like when you go to far towards a Ballmer Peak.
A little bit of culturing, some firm poking and prodding and analysis of older versions helped me discover Issue 28 on Github. What happened is:
run_suite had been changed to run in test-unit-2.4.9, which broke RubyMine
One of the JetBrains developers, Vlad, asks for a fix. His reasoning is that a maintenance version change is meant to be totally backwards compatible
Kou implements an alias for test-unit-2.5.0
Vlad asks why the minor version has changed, and asks about the versioning protocol which test-unit is using. He suggests the delightful Semantic Versioning because it’s awesome, and Bundler and (critically) Rubygems use it. So anyone saying they want gem "test-unit", "~>2.4.0" will never get the fix
Kou replies that they don’t use semantic versioning, and that Monkey Patching makes your code harder to maintain (well duh) and that, if you’re relying on it, you should specify versions (rather then have the package maintainer have to maintain monkeypatchability, much like how you’ve got that dick friend who chucks a tanty if you don’t keep their favorite brand of cracker in the house because they’re “ALLERGIC!!!!1!” to the others so suddenly it’s your problem not theirs. Ahem)
Kou then decides to remove the fix completely because he doesn’t want to maintain backwards compatibility, he wants to build new shit. He also offers a way to make their code work, that places the burden on them, not him
Good points from both sides really, because my opinion is coloured with the idea that monkeypatching is an atrocious idea. If you can possibly get away without doing it, don’t do it. If you do, then you’re responsible for ensuring it continues to work, not the packages you’ve patched. If this means you have to tell your customers to use a specific version, well, that’s your concern.
As for semantic versioning, I think it’s a great idea and to be a good citizen, you really should be using it. I’m big on standards and when a large majority uses the standard and you don’t, I think it behooves you to maybe consider if you can, especially for something like release versioning.
Anyway, the fix for all this is rather simple, just edit your gemfile to say
and wait for an updated version of RubyMine, with which you can use later versions of test-unit.
I’ve been playing with Devise for an authentication system for a project I’m working on (More on that when there’s something more solid), and I stumbled upon a heisenbug in omniauth-facebook, specifically in \lib\omniauth\facebook.rb, in the callback_url method:
def callback_url if@authorization_code_from_cookie '' else if options.authorize_options.respond_to?(:callback_url)
options.authorize_options.callback_url else super end end #THIS LINE HERE IS IMPORTANT end
When I’d add ‘puts"callback_url finished"‘ in the commented line, the defect would go away (And leave me with another, but that’s another show). Remove the line, and it’d be back. Now, this is proof that the intelligence of the observer effects if they count AS an observer. A smarter man then me would have been able to collapse the defect’s waveform and figure out what was happening. Can you see it?