Monday, 4 November 2024

Daily Driving Linux for Gaming - My experience

 It's been so long since I had a tech thing I thought worth of documenting, maybe I should do a tik tok about it, would make me feel less old doing so, but while many might TikTok this journey, I prefer the depth of a written post for now, so here we are.

Intro - why? (you can probably skip this part)

So Windows has been pissing me off for the last checks notes 10 years, Windows 7 was perfect I'd gladly lived in a world where Win 7 got updated for ever instead of of the debacle 8 was. Win 10 started the trend of trying to prove that my computer wasn't indeed mine, it belongs to Microsoft, I just paid for the privilege of using it. Ads enabled by default, which you can disable, but then a windows update will "accidentally" re-enable; the constant harvesting of my usage statistics/data; trying constantly to force Onedrive down my throat; installing apps without my knowledge or permission; the list goes on.

Windows even nagged me with full-screen ads for Win 11—only to tell me my rig was ‘not compatible.

And don't even get me started on the whole copilot and AI powered spyware win 11 comes with, that wasn't the feather that broke the camel's back, that was the artillery strike that left a smoking crater where the camel was.

With all these things combined, I've decided to give gaming on Linux another try, after all I've been keeping my eye on r/linux_gaming so things have been looking up

The Plan


  1. Install Linux on the "Spare Parts PC" I have in my office.
  2. Try to play my current games on it
  3. use it for a while until the next World of Warcraft expansion drops to see if I have any issue (yes, this was the most important step ๐Ÿ˜)
  4. Purge the main rig from windows and install Linux on it (dual boot is for cowards).
I use the Office PC a little bit every day, so any long term degradation and issues with updates would have been noted in the months leading to the expansion prepatch day (for those who don't know, the prepatch is when the game goes up on Major version and so any incompatibility issues will arise then).
This plan had a flaw however, the Office PC has a very old AMD GPU while my main gaming rig has some flavour of a RTX 2070 (which is Nvidia), a risk I was willing to take.

Execution

It's been a while since I had Linux as main driver, my last attempt at gaming in Linux was in 2018 and at work I've been using Macbooks since 2013 and deeming them "Unixlike enough" to bother replacing the OS on them, so I found a bit distressing trying to figure out which distro I should be using, so I went to the "old reliable" of Ubuntu and installed 22.04.
The installation was pretty smooth, followed by installing Steam and Discord which I did from the app manager. 
I had read that the best way to get Blizzard games installed was installing the B.net app via Lutris, so I went to install that but I stumbled on a comment saying Lutris on the app manager was several versions out of date, so I went to their site and grabbed the installer there, no issue.
While I was fetching things I also got ZSH for the terminal and my favourite theme to make it easier to use, but that's a tangent.
After installing Lutris, installing Bnet had a small hiccup on the installation steps which took me like 2 tries to realise I hadn't read the instructions on screen (remember the most important rule in IT: read the fucking screen), but after that I was amazed on how seamlessly everything went. Installing games directly from B.net and everything just worked. Back in 2018 I had been unable to get Overwatch working and it was a major issue since my wife loves that game and I love playing games with her, now OW2 just worked.
I even added the bnet app the the startup applications, I was happy that Lutris allowed us to start the apps without starting Lutris itself first (which was my expectation).
Then I installed the games I had been playing at the time on Steam, a few notable entries:
  • Hades II because it was an early access game I was expecting issues, but none were found.
  • Manor Lords had came out the day before and I got it in a spur of the moment buy, it just worked, was not expecting.
  • Helldivers 2 didn't work it threw a GPU error so bad it crashed the whole PC! ๐Ÿ˜ฑ
To be honest I hadn't been playing Helldivers 2 for a while as the game stopped being fun with the constant nerfs, I searched my issue for a while and concluded it was an AMD specific issue that people on Windows were also experiencing with that I decided to move and hope they patch the game and try it later or try it on the other GPU, but I gonna be honest, I never got around it I had stopped enjoying the game before I set myself on this path so I just can't bother.

I found a really weird bug though, the office pc connects to the same monitor I use for work, I discovered if I had wow+bnet running on office pc, but then on the monitor, swapped the input (in the input select, I do not mean swap cables) so I could see what laptop was doing, when I swapped back, the game would have closed ๐Ÿคจ.  I discovered this by accident as my use cases don't involve swapping monitor input that often, but it's really odd.

For weeks I was pretty satisfied with this, I would play games for a bit on the office pc and everything was great, I even did a twitch stream and everything worked fine (except for HD2, I found the issue on stream ๐Ÿ˜…).

Until one day...

One day the Bnet app just stopped launching. No error, just nothing. I found the logs from Lutris app, searching for that error didn't turn up much, people with similar issues years ago, some solutions included swapping the wine version to a newer one at the time which is now older than what I was using, all in that vein of not applicable.
I set B.net to be launched from Steam with Proton instead, that worked great, "small price to pay" I thought and moved on with my life, the office pc has been like that since.

Eventually I found an issue with Bluetooth headsets, I could not for the life of me pair one of my Airpods to the office pc - even when I used a USB dongle that I had used to pair the same ones to my (still) windows pc. Another BT headset I had laying around (PCX 550) had a bit of a hard time connecting, it would pair, often connect but not register as a sound device, requiring several bluetooth service restarts via the command line for it to work.
Googling around the issue I found some comments saying that 22.04 didn't have support for the faster/newer BT protocol my Airpods used, it came pre installed with 23.x though. It had instructions to install said dependencies, but I followed them to the same result. With this information in mind I decided I was going to try a newer version of Ubuntu on the main rig when time came.
And time came.

Going all in

By the time the prepatch came out and I verified absolutely no issues with it ❤️ Ubuntu 24.04 LTS was out, so I burned that into a bootable thumb drive and set to turn my main gaming rig into a Linux machine.
Before installing I did the try it thing  and checked that I could in fact pair my Airpods and they worked flawlessly on 24.04 - the next obvious step was to purge all of my drives and install it, what could go wrong?

I understand that I don't understand several things about the Linux ecosystem, things like Appimages, flatpacks and Snaps are new to me as they didn't exist last I daily drove Linux; there are things I don't know if I should complain about Ubuntu or Gnome; while going Ubuntu on my main rig went pretty much as I expected given the Office PC's experience, I have some critiques:
  • On 24.04 you cannot install a .deb file from the GUI by double clicking on it like you could before, I had to install another installer for that. Why? 
  • They changed the way the icons for the Dock are fetch, so most of my apps now have this default cogwheel icon, while I understand I can make a .desktop file for them and point for the icon, it's just fucking stupid, it is working fine on 22.04 why make it worse? If there was a change needed there to make it better why not use the old way as fallback?  This is a "we hate our users" kind of change.
  • Why do all the Appimages [that I use] need the "--no-sandbox" argument or they fail silently? 
    • Also, why so many things silently fail?
  • What's the deal with permissions and Snaps? I have some comics I read occasionally on a different drive (not an extarnal drive, just a slow high capacity one to store media), and I couldn't open anything with OpenComic or YACReader when installed via Snaps, this looks like a design decision, it's the kind of thing that steers people away from Linux.

Unexpected issues

On 24.04 I'm having a few fun little issues with World of Warcraft which still don't manifest on 22.04, such as the Dock and the taskbar on top deciding they should be over the game 

Note the dock on top of the game, and the annoying cogwheel icons.

Sometimes the place where the cursor is and the place the game registers it will differ by a several vertical pixels, you can tell this is happening when you're trying to click something and you click what's above it instead, both these problems however have the same solution, which is going into the graphical settings of the game, changing the resolution to "default" and pressing apply.

There's a constant focus issue where I launch the game and try to click stuff on it, but it instead brings to focus the apps bellow it on that position, I just started throwing the game to a different desktop and minimising the bnet launcher.

I got this ROG Mouse with a joystick on the thumb, I have it configured to be the arrow keys and I use it in a variety of games as such. However when I use it on WoW I get massive frame drop for a second, usually only the first time I do it. Can't figure out why, but I also can't change the joystick functions because the ROG software to change it doesn't work on Linux obviously, and Wine doesn't forward USB connections which is annoying, the only solution would be to run windows on a VM just to use this particular software, CBA.

If I close discord "to taskbar" meaning that it's still running, I have the icon the taskbar saying it's running, then I click the discord icon on the dock (which will have the little dot telling me about a notification), then I'll get a second discord instance instead of opening the one still running ๐Ÿคก

I did not expect the Nvidia drivers to be so behind on Ubuntu, which led me to manually downloading more recent one from Nvidia and installing them - which is fine. However, every time there's a big update my drivers get fucked and I loose graphics on restart, forcing me into terminal mode where I'll go in and reinstall the drivers from my downloads folder, I guess I'm glad for that terminal only installer.


Speaking of breaking the GUI, I tried to install a fancy video editor and it told me I was missing some sound related libs, so I go into terminal and start installing them, one in particular said it was satisfied by two packages and I had to pick one, so I did the smart thing and picked the one on the top of the list without any research on the matter obviously.  Halfway through the installation my screen goes black and stays that way. Restarting didn't help. I had to press F2 to force it into command line mode and after trying many things (and yes purging those packages was the fist thing I did), the only way I got my GUI back was doing `sudo apt-get install --reinstall ubuntu-desktop`, which also removed most of my apps. Luckily after reinstalling the apps they got their configs back from $home and even their icons were placed correctly in the Dock. This little side-quest highlights something I love and hate about Linux - your system is yours and you can do whatever you want, including fuck it up. However it can be so easy to fuck it up.

Final weird issue, for the longest time I couldn't use "´~รง^" and other characters from my native language, this was resolved this week or so by some update I didn't notice.

Conclusion

I'm likely not quitting Linux, these issues I faced have been annoying but not game breaking, in fact I didn't go into detail how pretty much every other game I threw at it just worked. Either through Heroic Launcher or through "Adding Non-Steam game" to steam I was even able to try some weird indie stuff from itch.io and it was rare that it took me more than one try to get stuff running. For example, in Darkest Dungeon 2 I had to restart the game after setting it to window mode.
But seeing me deal with these annoying little issues makes my wife not very keen in abandoning windows with me once win10 runs out of security updates next year.
Next I'm gonna give Bazzite a go, maybe I can get good results there.

Wednesday, 20 December 2017

ObjectifyHash Gem - My favorite over engineered solution for a stupid problem

Years after the initial development, I was finally allowed to bring this little gem to the public as part of my employer's effort to bring forth more open source code - hopefully more of these will come out soon.
Objectifyhash a very over-engineered solution for a stupid problem: dealing with nested objects on JSON responses from REST services. I find this specially painful when you need to navigate these representations, search for things in them and be able to do actions, one always ends up with code that looks like 

def find_thing_on_x things
  things.detect do |t| t['identification']['name'] == 'thing'; end
end
....
res = get 'https://my-service.com/stuff'

my_thing = find_thing_on_x res['data']['stuff']['things']

Not only is this ugly to look at, but very functional looking feels very un-Ruby like, not mentioning that on non-english keyboards like mine, brackets are hidden behind an awkward Alt Gr + numbers combo.
So, what I wanted to do in this case would be that either get the Stuff JSON mapped to a Stuff class on my Namespace so I can access handy Stuff related methods, or, if that doesn't exists, to get a generic object that allows me to handle Stuff more easily  AND (this is the fun part that other gems at the time didn't cover) repeat recursively for all nested objects inside.
That means, on the example above, it would create a Data class, a Data::Stuff and da Data::Stuff::Thing - since "things" is an array, it leaves it as an array, but each member becomes a Thing! 

The above example becomes much more beautiful with:

stuff = get('https://my-service.com/stuff').to_obj
my_thing = stuff.find_thing
OR
my_thing = stuff.data.stuff.find_things

I usually go with the second example here, as finding things is an action of Stuff and not from Data, but whatever.

So, all JSON objects become ruby objects of the class named after their key within the namespace of the previous key - easy, right?
It also has an exception handling, where you can specify several keys to map to specific existing classes on your code, this is how you inject your own methods such as the "find_things" above.

But this isn't even the part which makes it over-engineered, I opted to dynamically assigning methods to the instances with the values they should return, this means there's no setter methods and has brought some pains into the our work, the funnest of them all are when we have conflicts in Namespace or inherited methods from basic object, the supreme ofender: {"file": {"id": 123}} in which creating a File with an id method didn't work as aspected, so there's a few gotchas here , in this situation expect File_ and _id_ to happen, but overall I've enjoyed working with this tool.

Checkout the github page with more details and I hope there's more equally crazy people like me to enjoy this gem.

Wednesday, 3 August 2016

Things Developers say - But it works for this case

I love my developers, some of the smartest people I've worked with, but every once in a while they say something odd about why a problem isn't a problem.
(I really need to start writing these down when they happen for better posts.)

There's a feature on Skystore, where if you rent a movie but don't play it for 28 days, we'll send you an e-mail reminding you there's only two days left to watch. The subject of the e-mail is something like: "Only 2 days left to watch [movie name]".
As you can imagine waiting 30 days to test something like this is not feasible, especially for an automation enthusiast such as myself.
Luckily we have ways to trigger this e-mails before the time runs out, which leads to an e-mail with the following subject: "Only 0.0416666666666667 days left to watch".
I didn't bother to see the code on how two days turn into less than half a day if you force the service to run, but got some reassuring that this only happened if you force to service to run earlier. It's fine, we log a bug on the issue tracker the same, just not a "fix it now" kind of bug.

A couple days go by, a dev comes back to me:
- You know, that has to do with the time left calculation, if we wait 28 days the e-mail will look well.
- So you're telling me it's fine because for the current business requirement that calculation happens to give a whole number?
- Yes.
- This value is configurable, so if someone decides it's supposed to be 3 days instead of 2, we change the config value and our customers start receiving mails telling them they have 2.333333333 days to watch?
*blank stare* - Ok we'll take care of it next sprint.
Yes I did say "3" that many times out loud.
 

Wednesday, 29 June 2016

Watir Webdriver / Selenium HTTP Authentication - if it's stupid and it works, it ain't stupid

I've taken some time to write a small sanity test pack to run on the web app that uses the platform my team develops.
Although we have a very good and thorough automated test coverage, I'm very paranoid and want to make sure we didn't miss anything that breaks basic functionality, not that it happens often, or in recent memory, I'm just paranoid - it's a good quality for QA person, right?
So I went about the correct way of doing things, Page Object Model; I have a basic page object that has functionalities that are relevant through the whole app, like knowing when page is fully loaded, how to login, etc. Everything else inherits from this class.
First problem, http authentication, selenium and watir still (!!!) don't have a proper way of dealing with this well, so I went with the normal "https://username:password@url." method, which by the way, nobody else tells you this: use URI encoding on the username and password.
But then I got to the playback page on our app (which is kinda an important page if you're online movie retailer), for reasons not important for this post, on this page there's a https -> http redirect, this redirects cause our username:password deal to be lost and I got stuck with the authentication popup.
Googling how to get around this today still gives me about the same answers as a few years ago, which include this lovely (yet old) Watirmelon post that suggest using AutoAuth plugin for Firefox, usually I'd be against this, since it binds my tests to Firefox, but since for my particular sanity-for-other-team kind of work, I don't really mind, however AutoAuth no longer works with recent versions of Firefox, it stopped working on v39, currently we're on v47.
After much dive into documentation and stackoverflow questions, I gave up, I was ready to ask them to turn off HTTP Authentication for that page, until a co-worker casually mentioned:
Try opening the http version with username and password on the url first, at the start of your test, then go to the normal https start page and do the normal test, the browser will remember when you get redirected.
That a stupid solution... but it works, so it ain't stupid.
Here's how my code changed:

      [...]
      browser.goto inject_auth( url ) if url
      wait_for_load
      [...]

to:

    if url
      browser.goto inject_auth(url)
      wait_for_load
      browser.goto inject_auth(url).sub( 'https', 'http')+ 'playback/fcf7ce62-5695-4d80-be85-662798c9ac7c?token=undefined'
      wait_for_load
      browser.goto inject_auth(url)
    end

Hacky, I know, I have to load the normal page first, because I set up the http load purposely to hit a 404 so this wouldn't cause any unintentional side effect, but it also gives me another redirect, only after this little dance I get to go to the initial page I intended.
Hope this helps other people trying to automate apps with redirects.

The Watirmelon post I mentioned before resolves the problem with NTLM  auth, which could be applied to everything, however my solution doesn't solve that particular problem, if you find out how, please let me know.

Monday, 18 April 2016

The importance of using proper naming in your tests

The Skystore project, which I'm the lead tester for, still has a lot of unreleased features, while the reason for this are beyond the scope of what I intend to talk here, one particular feature brought down an important, albeit obvious lesson: Naming matters.

One feature that has been worked a while ago, but is still unavailable, is called "playnext"; it's a simple feature: when giving out video playback information to the client, the server will include some information about what to suggest the user to watch next, some time before the end of the video, the client will display a little box showing that information with an easily accessible play button.
This feature has several tests along with it as what should be suggested varies in accordance with different conditions which have all to be tested and made sure the server is giving out the correct info and structure.
As an automation evangelist I've automated the shit out of this feature, every normal, erroneous, corner and edge case was covered and everything was saved into GIT, available to everyone and runs everyday on every integration environment - good times.

After client applications implemented their side of this feature and found it lacking, it was decided that a new feature would be implemented server side, it would behave just like playnext, but with a different structure that would better satisfy the needs of the clients giving more complete and extendable information a "playnext 2.0". Since different teams have different priorities and implementation times, it was decided that the two features would run side-by-side for a while, to preserve those who had already implemented the first version.
When deciding which tests would be needed, I've said to my minion responsible for it: "Look at the playnext tests, copy them, adapt to the new structure, see if there's anything missing or need to be removed and you should be done quickly".

Later the feature was developed and tested, approved and delivered upon the client teams. Then they noticed that there were several missing parts and it was mostly unusable.
How did this pass the tests? It was obviously broken! My minion told me "I did what you told me, I've copied all the tests I found, there weren't many though"

I've looked for playnext in the feature files, found only a couple of tests, but I know I wrote a bunch of them, what happened? I looked into one file I knew had a few of the tests and found the problem:

play next... play next play next....

There it was, in the tests I named the feature "play next" instead of "playnext", when searching for the correct name, only a couple tests had it.
This explains why he thought there weren't many tests.

So, when naming and describing your tests and features remember:

  • Describe your tests using the correct feature name.
  • Make sure to use the name consistently (ie: don't change the name mid way).
That's it. After this event the tests were corrected, the missing testes added, the feature was patched and integrated with and everybody is happy, hopping it's well received when available to a wider audience.

Saturday, 15 August 2015

Using Dropbox to sync saved games (and more) across multiple computers

Over three years ago I made a post on Reddit explaining how to do this, since then Steam introduced cloud saving, so all saved games were sync'd without a second thought, I kinda grew used to it. Long passed is the time when I'd backup my saved games on CD's and DVD's with fear of losing all those game hours to a computer failure.
However I got hit with this problem recently when I bought Banished on GOG, while it's great that they sell all the games DRM free, it's a shame they don't have cloud saving yet, so I went back to the old technique and decided to share it on a more permanent place than reddit.
Please note that instructions cover Windows 7, Linux and OSX, now Win 8 or 10, honestly I don't know if shell link extension works there, if there's an alternative and as of now, I don't care.
The original post:


I started using this technique to sync saved games so I can continue my massive Civilization games when my gf is sleeping at her house. It's the same I've used for years to sync my pidgin settings and .vimrc across my linux machines, but yeah using it for gaming:
First of all you need [...] Dropbox (why on earth are you not using this yet anyway?) make and account and stuff. If you're on Ubunto it's available on the software center thinggy.
Windows 7 only: Get Shell Link Extension 
(my pictures examples are with wow addons and settings on windows 7)
Now find what you want to sync and MOVE it to a folder on dropbox:
In this case I'm moving the Addons folder
On windows, drag the folder back to where it was with the right button and select Drop Here -> Symbolic Link, it will pop up an authorization thinggy, but it's ok.
On Mac and Linux pop open a console and use the "ln -s /path/to/drobox/place/where/you/put/your/stuff /original/path/thing" command
And you're done! Yey
Bonus, you can share folders with your friends so you all can use each other's computers to play or whatever, for instance, next I'm making sure me and my gf can play WoW on any of our computers with the same addons and addons preferences.

Not that I condone this kind of thing, but if you got you pc games at the special Bay shop and thus don't have steam, you can use this technique.

Thursday, 26 February 2015

Things Developers say

As a QA person I've been told by developers a myriad of rather amusing things or funny conversations caused by failed tests and the all too often "works on my machine".
I've been meaning to compile them, but I'll just start making posts as they happen/I remember.

All names and locations have been removed for privacy of those involved.


$Dev: On $ticket you mention that the $behaviour happens when it shouldn't. However on the requirements it says $behaviour is expected.
$Me: Oh, I must have missed that, where does it say so on requirements?
$Dev: Well, something things are common knowledge and shouldn't need to be written anywhere.

$QA2: This doesn't work
$Dev: Of course it works, look *press button on his laptop* it works, you're testing it wrong.
$QA2: But that's your machine.
$Dev: So? It works.
$QA2: "Works on my machine"
$Dev: It works!
$Me: Great, $Dev we should tell $Boss to arrange $Ops to have your laptop sent to $location.
$Dev: Why?
$Me: Your machine will be the new prod server obviously, since it only works on your machine.

At this point $Dev helped $QA2 to find the root cause of the problem, things were indeed broken.

$Me: This doesn't work when you use $resource_type.
$Dev: Can't reproduce, did what arguments did you use? Can you retest please in the RC and master branch please?
$Me: I used the arguments of a $resource_type, here's the error stack trace on both branches.
$Dev: I used $other_resource_type and it works, are you putting the arguments correctly? Or are you using dummy/broken data?
$Me: I used $resource_type, if I had used bad data I'd have said so in the first place. Just try $resource_type.
*couple minutes go by*
$Dev: Ok this doesn't work, I'll fix it.

Friday, 30 January 2015

Ruby: Custom Errors for Lazy Bums with const_missing

Catching errors is quite a normal thing when writing code, every programing language has some variation of the throw/catch, in Ruby's case, it's the elegant begin, rescue and ensure blocks.
At some point in every project you stop catching "other" people's errors and start catching your own.
So you start creating you own error classes, there's a couple common approaches I've seen over the years.

  • Create an Errors namespace on each class/module you need and create errors for that there.
  • Create a generic Project::Errors with its own file bag and throw all errors in there.
I usually go for the second approach, so error classes don't distract me, but that's more of a personal preference anyway.
However, I am a lazy bum and I've got tired of specified my error classes, so I made this Gist as an example:


The main idea behind this, is letting you freely type your errors as you need them both where they're being raised and rescued and you don't need to worry about they existing since they'll be created in runtime for you.
This method is probably not very efficient, but I don't care for this case.

Wednesday, 10 December 2014

JUnit test reports not shown on Jenkins for Failed Jobs that never passed

*sigh*

This one costed me a whole morning of work.
I've been reorganizing my Jenkins test jobs so they're more friendly to outside teams, I've decided to be cautious and not just delete all the existing jobs, instead I've created a branch on my test repository, I made a lot of changes, then, I created new jobs on jenkins fetching from that branch, also made a new Dashboard that picks up those jobs. Ideally, the new dashboard should have about the same number of tests as the old one.
To my dismay, the new dashboard registered ~200 tests while the old one had over 1k.
All configurations were exactly the same (in regard to publishing results) from jobs to jobs, but some had the info right there on the dashboard, some didn't, worse, the ones that didn't, had the HTML report just fine.
I noticed that the jobs that didn't have the numbers were failing, while the others were passing/pending.
Digging through the internet yielded nothing particularly useful, I knew the problem laid somewhere between Jenkins and JUnit test reports.
I went through XML + XSD validators on the tests reports generated, but nothing was wrong.
After a coworker joined the struggle, he noticed that if we forced one of the failed builds to pass, all of the sudden  all the info would magically show up - including the ones from previously not shown builds.
I started asking folks I knew used a similar setup as I do and got this fantastic insight (paraphrasing):

If you're using Jenkins ver > 1.581 you're screwed. They've split the JUnit reporting into a plugin, it breaks like that after version 1.2, but Jenkins forces you to update it after version 1.581.
So I had two options, either I went through my 44 jobs and forced them to pass once and then went through them again to revert OR I'd downgrade Jenkins and JUnit plugin.
Once again I'm reminded of a sentence I hate "If it works, don't update it".
In case that version of Jenkins or the plugin aren't downloadable anymore, I've made a mirror on Dropbox.


Wednesday, 8 October 2014

Dynamic Class definition with inheritance in Ruby

If for some reason you need to have a class Dynamically defined in Ruby the following code will do you fine:

2.0.0-p247 :001 > dynamic_name = "ClassName"
 => "ClassName" 
2.0.0-p247 :002 > Object.const_set(dynamic_name, Class.new { def method1() 42 end })
 => ClassName 
2.0.0-p247 :003 > ClassName.new.method1
 => 42

However it might be useful to know that Class.new accepts another class as parameter for making it the class' parent, so if you want to make a dynamic class with inheritence you can do this:

2.0.0-p247 :001 >  dynamic_name = "ClassName"
 => "ClassName" 
2.0.0-p247 :002 > class A; def method1() 42 end; end
 => nil 
2.0.0-p247 :003 >Object.const_set(dynamic_name, Class.new(A)  )
 => ClassName 
2.0.0-p247 :004 > ClassName.new.method1 #=> 42
 => 42


This is slightly akin to dark magic, try not to use it much or at all... Now excuse me I need to summon some imps.

Monday, 9 June 2014

Measuring Response Time With HTTParty

If, while using HTTParty you ever wanted to know the duration of a request you probably resorted to saving the timestamp before the request and comparing it with after the request. I've got tired of this arcane way* and made a gem that adds a #duration to the response object.
Introducing: HTTParty-Timed, it's easy to use, just replace your require 'httparty' with require 'httparty/timed' and tadaaaah you're done! Now your response object has a method which returns the duration in milliseconds.



*Actually I just hid the arcane way deep inside NET::HTTP with a bit of monkey patching, so beware if you're not using "stock" NET::HTTP or HTTParty.
The good news however, is that this does not count with DNS resolve time, which in Ruby's implementation of NET::HTTP is always done (ignores OS's dns cache) on each request, which would add overhead time to your measurements

Github link

Monday, 24 March 2014

Void Value Expression in Ruby

Here's a commonplace scenario for almost anyone:

variable = nil  
if other_var == 1 variable = 2 
  elsif other_var == 2 variable = 3 
end
Simple right? Ruby however allows you to short this a bit  (with arguable readability):
variable = if other_var == 1
             variable = 2
           elsif other_var == 2 
             variable = 3
           end
This works because Ruby always returns something from evaluated blocks, even "if"'s. Now, what if you needed a return out of the function during that if?
variable = nil
if other_var == 1
 variable = 2
elsif other_var == 2
 variable = 3
else
  return "Let's get out this way, it's quicker!"
end
One would think he could do this:
 
variable = if other_var == 1
            variable = 2
           elsif other_var == 2 
            variable = 3
           else
            return "Let's get out this way, it's quicker!"
           end
2.0.0-p247 :037 > variable = if other_var == 1 
2.0.0-p247 :038?> variable = 2 
2.0.0-p247 :039?> elsif other_var == 2
 2.0.0-p247 :040?> variable = 3 
2.0.0-p247 :041?> else 
2.0.0-p247 :042 > return "Let's get out this way, it's quicker!" 
2.0.0-p247 :043?> end SyntaxError: (irb):43: void value expression
Nope! When I first saw this error it kinda reminded me of a similar error Perl would return when you forgot to add 1; to the end of your file, so I tried:
2.0.0-p247 :047 > variable = if other_var == 1 
2.0.0-p247 :048?> variable = 2
2.0.0-p247 :049?> elsif other_var == 2 
2.0.0-p247 :050?> variable = 3
 2.0.0-p247 :051?> else 
2.0.0-p247 :052 > return "Let's get out this way, it's quicker!"; 1; 
2.0.0-p247 :053 > end 
 => 2 
2.0.0-p247 :054 >
I think this might be a problem with the compiler.

Thursday, 30 January 2014

Speeding up Cucumber and Concurrency tests using Threads

Reading on how to make Cucumber tests faster, most literature recommends using more CI servers, or more CI nodes, and that kind of solutions. I've always found this kind of solutions very expensive, not that machines are particularly expensive nowadays, but because more machines/nodes = more maintenance and, if you're on a particularly big organisation, are very annoying to acquire.
One thing that's much more efficient and should be done before getting more machines is simply using Threads.
However, you can't simply open a new thread on every step and rejoice as your tests take 1 second.
What you can and should do, is identifying small peaces of code that could be split in different threads. How do you know that?
The easy ones to spot are iterations where you don't care about the order in which each iteration is executed.
For example, I have this test against an API, which calls an endpoint and verify that every link returned is valid. This is a very simple and obvious case that follow on what I mentioned above, since:
  • I have a list of things.
  • I will iterate this list and do some kind of check on each item.
  • I don't care if item "B" is checked before or after item "A".
So, to speed up a test like this, I can start a thread on start of each iteration, give it the checks I want it to do, saving the thread reference and go to the next item. After sending all items to their threads, wait for all threads to finish.
Luckily, we're using Ruby, where this kind of things are very easy to make, here's a gist code example:


Earlier I set off to try this technique, but, before, I ran a scenario on my IDE that would call the original code, these are the results:
1 scenario (1 passed) 
5 steps (5 passed) 
0m23.459s
After changing the code I got:

1 scenario (1 passed) 
5 steps (5 passed) 
0m10.408s
Wow! Over 50% improvement! This is too good maybe, so I hammered some code to force it fail - I was afraid for a moment that this setup was hiding test failures, but no:

1 scenario (1 failed) 
5 steps (1 failed, 4 passed) 
0m11.366s
Pretty happy with myself I pushed my changes to my git repo, and tried to run the Jenkins job which had a few tests with this step and others without. To my amazement, the previous 3 builds had took around 10-12 minutes, the new build took 2 minutes (yes, two minutes), that's a very big performance increase.
This technique can be used for other things as well, for example, you can have a step which tests race conditions, having two users on two different threads trying to do the same thing at the same time while you make sure they don't step on each other's toes unintentionally. I deally this race conditions tests is more of a Rspec/Unit test kind of thing that falls a little out of place of Cucumber, but knowledge of how to bend your tools never hurts.
Also, this will probably be fun when I join this technique with the two simultaneous browsers one.

Wednesday, 15 January 2014

Simultaneous multi user browser testing with Cucumber and Watir Webdriver

This post is not about parallel testing (with the intent to reduce tests execution time), nor about running tests in many different drivers. For those issues I recommend looking into Selenium Grid (you can start here) and Jenkins nodes.

I haven't seen many (any) implementation of tests where you control two browsers at the same time and my googling of the subject yielded no useful hints on the subject. I find this odd, with the ever increasing user interaction web apps have, how come there aren't more automators with needs for tests that require two users to talk to each other (for example) through two distinct browser windows?

Before we can even begin, there are few caveats, this won't work if you like the using the browser handing through "$browser" global variable on you test suite like many webdriver examples show you to. Personally I find that kind of usage is terrible to maintain, very inflexible and I'm biased against global variables - like Matz (Ruby's creator) said: "They are ugly, so don't use them".

For a long time I've been recommending people to have the browser handling done at the World level, I  setup this example a while ago, it is a module that you add to your World and it takes care of browser handling in a more pretty way. If you use the browser method it will start your browser as needed, so you can close and not worry about the state of a global var and where it got initiated...
This is pretty and good, unfortunately having the browser at World level like this means we only have one browser handler.

My favorite pattern for Cucumber based test frameworks however (and yes, I am aware it escapes a bit the few suggestions found in good literature) is making the World as an instance of a class made especially for handling the application at hand. This has it's advantages and drawbacks - and I obviously think the drawbacks are negligible facing the advantages.
How does this help?
You can have a list of users, each user being an instance of "YourApp::User" having this class include the Browser module, this way every user has it's own browser - using Firefox default configuration on Watir won't have the cookies "bleed" into one another.

However this does create another problem into itself, it probably screws up with most screenshot-on-error handling. I have made a gist with a proper solution (well, at least works for what I need).

How it works:

  • Use the method browse to pass a block which gives you the browser handler.
  • The method browser is still available, but not so cool.
  • The browsing is wrapped in a way that on error a screenshot in base64 format will be saved to a file.
  • Why base64? Embedding images on Cucumber HTML report needs to have the image accessible through an http request for as long as you want the report accessible.
  • Ok, why a file then? Just a way to create an image *somewhere* and have it available on hooks, it also makes the image available case something goes terrible wrong and you hook block doesn't fully run.

Caveat: I haven't tried the multi-user thing with many browsers at once, for my needs this has only been done in FF, I'll probably give PhantomJS driver a try with this and see what happens, if you do try this with Chrome, IE, Safari or Opera drivers, please tell me how it went in the comments.
I am aware that this probably is probably a "Lisa Simpson" - answering a question no one asked.

Wednesday, 8 January 2014

Getting OSX, Jenkins, Cucumber and RVM to play together.

Recently I've changed my corporate laptop for a corporate Macbook Pro, good bye Windows based Ruby coding problems! Hello OSX problems! 

The hardest part was getting my local Jenkins working with my RVM installed Ruby and gems, my solution for those nasty "cucumber command not found" problems was a little ugly, but works wonders.
I started with echo $PATH on the console and copied all Ruby related parts, then I created a .jenkins_profile file with:

[[ -s "/Users/[MY_USER]/.rvm/scripts/rvm" ]] && source "/Users/[MY_USER]/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
export PATH=/Users/[MY_USER]/.rvm/gems/ruby-2.0.0-p247/bin:/Users/[MY_USER]/.rvm/gems/ruby-2.0.0-p247@global/bin:/Users/[MY_USER]/.rvm/rubies/ruby-2.0.0-p247/bin:/Users/[MY_USER]/.rvm/bin
export GEM_HOME=/Users/[MY_USER]/.rvm/gems/ruby-2.0.0-p247
export GEM_PATH=/Users/[MY_USER]/.rvm/gems/ruby-2.0.0-p247:/Users/[MY_USER]/.rvm/gems/ruby-2.0.0-p247@global
The first part is the rvm exports that I have on my own .bash_profile; then exporting a new $PATH with the Ruby info I copied earlier; ending with a forcing gem home and gem path for good measure.
Unfortunately for every job configuration I have to start with source /Users/[MY_USER]/.jenkins_profile, but it works fine and the local jenkins always has the gems I use.

Hope it helps more folks.

Tuesday, 15 October 2013

HTTParty with cookies

On the past few months I've been doing automated testing work on an rest API that happens to rely a lot on cookies for user authentication stuff.
HTTParty is a great gem for working with rest API's, it's easy and simple, but it doesn't provide a care free way of dealing with cookies. I mean, you have a way to read the cookies from the response, you have a way to add them to the request, but you'll have to do it manually. So like any good Rubist I've made the code for it into a little gem. 
I hope it helps someone else as well. 
You can find most of the information on github.

Thursday, 11 July 2013

HTTParty and non 200 response codes, cache & etags and a few other quirks

I've been playing with HTTParty to help me automate tests for an API, while its is a great gem, I found some documentation and guides lacking.
What happens when you add to your request an ETAG (for cache) and the server returns an 304 (not modified)? I'll tell you what happens, you'll get a "nil"!
No really, you get a nil!
Now, this confused the shit out me, and I've googled on and on about it, but found nothing really useful, until, for some reason I found deep into the specification my answer:

    describe 'with non-200 responses' do      context "3xx responses" do        it 'returns a valid object for 304 not modified' do          stub_response '', 304          resp = @request.perform          resp.code.should == 304          resp.body.should == ''          resp.should be_nil        end
"should be nil?" Well, that make no sense! It turns out that your nil object has some methods injected into it, because... I don't know, someone thought it was intuitive and easy - I .... disagree...

So, how we deal with this?

Something like:
res = HTTParty.get 'someurl', headers:{'If-none-match': 'some e-tag you got from earlier'}
res.code #=> 304
res.class #=> Nilclass





This is also valid for other responses with no body that HTTParty thinks is fun to turn into nils, because if you don't have the body for it, you can't party.

Wednesday, 8 May 2013

Pi Facer gem version 0.0.1 alpha released

I've been playing with a Raspberry Pi and I have this project that might use the GPIO pins, I've poked around to see if any Ruby gems allowed me to use it, I've found this good one that has an event driven way of doing it.
However later I decided to use a Piface instead of the pins directly, unfortunatly there were no event driven gems for piface, which is a shame, because I figure it's the best way to interact with it. So I made one.
I've based of piper because all the threading stuff was already done, hope it helps more people with piface projects.
Oh yes, Ruby 2.0.

pi_facer on github, pi_facer on rubygem.

Friday, 12 April 2013

Using Watir-webdriver and selenium remote control to automate tests on Internet Explorer

So, you're on a Linux machine (like a boss), you have a pretty sweet battery of automated tests that you run against your web application on your CI server (which also runs Linux), but you have a few problems:

  1. Internet Explorer sucks and most defects are open due to it.
  2. You can't automate IE because all your systems are on Linux.
  3. You need to automate on IE, because 13% of your user base are either ignorant or corporate drones who can't change their browsers.
So what you do? You can't just quit and call it a day? Nope, you keep reading.

What you need?
  1. A windows machine (yeah, sorry about that, but you knew it was coming).
  2. Install on it selenium remote grid server on said machine, here's a guide how to set it up.
  3. Our good friend Ruby!
  4. selenium, selenium-webdriver and watir-webdriver gems.
That's great, but you're wondering "How do I make watir-webdriver connect to the remote selenium hub?", that's a great question, for that all you need it's to pass it the arguments you'd use for selenium-webdriver to do that. And to be honest I only know that because I've been digging into watir-webdriver's code on github and I know that all parameters you pass it are passed along to selenium, something that's not quite clear on watir's documentation or any guide you can find on the web.
What I needed was something that would have watir-webdriver connect to Firefox locally, but if I wanted IE it would connect remotely, so, thanks to the param ":desired_capabilities" you can specify which browser you want to open on your remote connection.

I've created a sample for this on github, which you can find here.

Thursday, 14 March 2013

Installing Spotify on Fedora - now with a installation kit

Edit: These instructions are now almost one year old, if you're using Fedora 17 or newer try one of the links for the community packages on the bottom of this post or in the comments section.

Me and coworker compiled instructions to install Spotify on Fedora and packed them in a nice little tar file for everyone to use.
Get the file here:

Instructions (On the README file): http://dl.dropbox.com/u/17266999/spotify-install-kit.tar
(If you rather not install stuff you got from dropbox, you can still follow these instructions since they have the steps for not using the file I'm linking.)


yum install wget rpmdevtools rpm-build yum-utils cpan
rpmdev-setuptree

#get stuff from interwebs if you don't have it, skip if using "compile kit"
wget http://leamas.fedorapeople.org/spotify/0.8.8/spotify-client.spec

#install dependencies
spectool -g spotify-client.spec
yum-builddep spotify-client.spec

#compile Alien
git clone Alien.git (or the url for the git repo if you're not using the "compile kit")
cd Alien
./Makefile.PL
make
sudo make install

#build the rpm
cd ..
cp * ~/rpmbuild/SOURCES/. #don't use -r !

env QA_RPATHS=$((0x02|0x08)) rpmbuild -bb spotify-client.spec
sudo yum install [rpm] #at the end of the build the output lists the .rpm file it wrote, copy paste that

#Launch!
spotify

On a Fedora 18 64 bits machine these instructions just worked
On Fedora 16 64 bits I had to remake the links for a few of the .so files because it was originally pointing for /usr/lib64
 but locate found them on /lib64

Links:
http://community.spotify.com/t5/Help-Desktop-Linux-Mac-and/Linux-Fedora-RPM-package-for-F17-F18/m-p/237688#M8446
http://leamas.fedorapeople.org/spotify/
http://repository.spotify.com/pool/non-free/s/spotify/
https://github.com/leamas/spotify-make