Quantcast
Channel: Badoo Tech Blog
Viewing all 132 articles
Browse latest View live

Calabash Android - Unleash the power of Query

$
0
0

Calabash provides query method to get views/elements from screen. But query can do more than this. In this post, I’ll go through a few operations we can perform using the query method invocation option. This can be used for a range of tasks from updating simple views to calling system services.

You can read basics of the query operations in the Calabash wiki. In addition to getting results from the screen, we can use the query method to update views. This can be simple an EditText or a DatePicker.

Please download the application from Calabash Test and open the Calabash console. You can try the examples as you go along

$ calabash-android console CalabashTest.apk
irb(main):001:0> reinstall_apps=> nil
irb(main):002:0> start_test_server_in_background=> nil

I’ve created a sample registration screen that looks similar to the Badoo one. I’ll explain how to enter data in this form using the method invocation.

Input fields

EditText fields are used as input text fields in Android. You can enter an email into EditText by running the following command:

irb(main):001:0> query("EditText id:'email_input'",:setText=>'me@badootech.london')

The EditText Api has a setText() method which takes a single argument (actually inherited from TextView). In the above example, we invoke the same operation with a string value.

Arguments are passed to query work in chain direction. Each argument is invoked on the previous argument result. To read more about query directions, check this post: Query Direction and Layouts.

Whenever we want to invoke a method with a single argument, we can call on the selected view using this syntax :operation=><value>. Please note how the value is passed to the operation. If it’s a string, it should be passed with single/double quotes.

You can get the text value of a view by invoking the getText() method. If the method has no arguments, then you can call on the selected view using the syntax :operation.

irb(main):001:0> query("EditText id:'email_input'",:getText)

The query also has a shorter way invoke the ‘get’ operation. Try this:

irb(main):001:0> query("EditText id:'email_input'",:text)

When you call :operation without ‘get’, it tries to find one of the methods operation , getOperation or isOperation on the view and invokes the first available one.

Update the CheckBox and RadioButton

The CheckBox and RadioButton inherit from a common view and both have the method called setChecked, which take a boolean value (true or false).

irb(main):001:0> query("CheckBox id:'check_update'",:setChecked=>true)

Boolean values are expected to be passed without quotes. Similarly, you can select the gender in the registration screen by using one the following queries:

irb(main):001:0> query("RadioButton id:'radio_male'",:setChecked=>true)
irb(main):001:0> query("RadioButton id:'radio_female'",:setChecked=>true)

It’s highly recommended that you use the touch operation for selection of these views (it may trigger onClick() call back implemented in the app code) but you can also invoke these methods.

To get the value of the check box, we should invoke the isChecked() method. If you call :checked, it will eventually find the isChecked() method and call it.

irb(main):001:0> query("CheckBox id:'check_update'",:isChecked)
irb(main):001:0> query("CheckBox id:'check_update'",:checked)

Set rating on RatingBar

The RatingBar is one of the views where it is difficult to trigger touch operations. It is easier to invoke the method to set the desired value.

irb(main):001:0> query("RatingBar",setProgress:4)
irb(main):001:0> query("RatingBar",:setProgress=>4)

And to get the value of the RatingBar

irb(main):001:0> query("RatingBar",:getProgress)

Set date in DatePicker

So far we’ve seen methods that accept a single argument. Now I’ll explain about the methods that accept multiple arguments. The best example is DatePicker. First, touch the date display view to open the data picker dialog.

irb(main):001:0> touch("* id:'dob_text'")

You can do query(“*”) to find all the elements on the date picker dialog. The main element is the one with id datePicker

irb(main):001:0> query("DatePicker id:'datePicker'")

In the Android Api, DatePicker has the updateDate method with three arguments year, month and day. To set the date, we should call this method on the DatePicker view.

Now set any date for ex: 30-11-1990 (day-month-year) by running the following query

query("datePicker",:method_name =>'updateDate',:arguments =>[1990,11,30])

Query has the option to specify the :method_name and :arguments to invoke on a view. The arguments should be passed as an array. You can also try following way to set the values for EditText, CheckBox and RatingBar

query("RatingBar",:method_name=>'setProgress', :arguments=>[5])
query("EditTextid:'email_input'",
          :method_name=>'setText',:arguments=>['me@badootech.london'])

Similarly, to call methods with no arguments, the arguments value should be an empty array:

query("EditTextid:'email_input'", :method_name=>'getText',:arguments=>[])
query("RatingBar",:method_name=>'getProgress', :arguments=>[])

Summary

In Calabash, query is a powerful tool to interact with views. It uses Java reflection internally to invoke operations on views. It can also be used on custom views. It’s important to know the method and its argument types before calling it. Unfortunately Calabash doesn’t provide proper error messages for method invocation, so passing the wrong number of arguments or argument types won’t update the view.

But this is not all. Query can do much more than this. In the next post, I will explain how query can be used to interact with system services to get more details about the device. If you need methods for other views or something that is not clear in this post, please help me make it better by giving me your feedback in the comments.


Can you center a grid of images using only CSS?

$
0
0

In this article I’d like to discuss a UI problem I was faced with in the past, which I still have no elegant solution for. It seems completely reasonable for this problem to have a simple CSS solution, yet I have not been able to discover one so far. I ended up solving the problem in Javascript; it felt wrong and it felt like a cop out.

I first came across this problem around the time I started interviewing candidates for mobile web positions at Badoo, so I thought, why not kill two birds with one stone? After roughly 20+ interviews, most candidates are able to solve the problem, but they all resort to Javascript after failing to complete the problem using only CSS.

Is it possible? It is such a simple UI pattern we see used on the web all the time. Let me talk you through the problem:

Problem

We want to be able to render a grid of images in such a way, that:

  1. The grid items are square and have a fixed width and height, with a fixed margin surrounding the image.
  2. The maximum amount of grid items should fit into the given screen width.
  3. The grid itself should be centred on the screen, thus the margin on either sides of the grid should be the same.
  4. The items in the grid are left-aligned.
  5. The amount of grid items are not predetermined.
  6. The screen width is not fixed (ie. users can resize on desktop or change the device orientation on mobile)
  7. This grid should work on all smartphones and desktop browsers.

If we depict these requirements graphically:

Output

And if we look at this problem mathematically, we see that the equation we are trying to satisfy is:

where:

Our end goal is to render something like this on multiple devices in either portrait or landscape satisfying all the requirements in the list:

Output

Markup

Let’s start by creating simple HTML markup and CSS as a starting point:

HTML:

<ulclass="grid"><liclass="grid-item"></li><liclass="grid-item"></li><! -- more grid items --></ul>

CSS:

.grid{padding:0;}.grid-item{float:left;margin:2px;width:95px;height:95px;background-color:black;}

This would output the following:

Output

And it violates:

Nr 3: The grid itself should be centred on the screen, thus the margin on either sides of the grid should be the same.

‘Solution’ 1

Usually people’s first impulse would be to add text-align: center to .grid. I guess it’s fair enough to try that initially, although at the moment our grid-items are floating left and adding text alignment will actually have no effect. Let’s play devil’s advocate anyway by removing float: left; from .grid-item and replace it with display: inline-block; and add text-align: center to .grid:

Output

This is not a bad solution, it ticks all our boxes, except:

Nr 4: The items in the grid are left-aligned.

‘Solution’ 2

People then have an ‘aha!’ moment and add margin: 0 auto to .grid. This has no effect as the grid has no fixed width, so no margin can be automatically calculated.

Which then leads people to start adding fixed widths:

Fixed width v 1.0

Let’s assume an iPhone 6 Plus and changing .grid to:

.grid{padding:0;margin:0auto;width:396px//fourboxeswith95pxwidth/height+2pxmargineitherside}

we get what we wanted!

Output

but as soon as we change the orientation of the device we get:

Output

so now we are failing:

Nr 2: The maximum amount of grid items should fit in the given screen width.

Fixed width v 2.0

Well that’s not a problem, let’s solve it by adding media queries:

.grid{padding:0;margin:0auto;}@mediaonlyscreenand(min-device-width:414px)and(max-device-width:736px)and(orientation:landscape)and(-webkit-min-device-pixel-ratio:3){.grid{width:693px;//sevenboxeswith95px//width/height+2pxmargineitherside}}@mediaonlyscreenand(min-device-width:414px)and(max-device-width:736px)and(orientation:portrait)and(-webkit-min-device-pixel-ratio:3){.grid{width:396px;//fourboxeswith95px//width/height+2pxmargineitherside}}

So now it works on landscape on iPhone 6 Plus as well!

Output

But we still have that last requirement:

Nr 6: The screen width is not fixed (ie. users can resize on desktop or change the device orientation on mobile)

And no, please do not suggest adding even more media queries.

Fixed width v 3.0

Now this is where people start bringing Javascript into the solution. Using the equation set out earlier, we can calculate the container width by:

JS

(function(){varmargin_=2;varboxWidth_=95;varcontainer_=document.querySelector('.grid');functionsetGridWidth(){varboxSize=(2*margin_)+boxWidth_;container_.style.width=(Math.floor(window.innerWidth/boxSize)*boxSize)+"px";}})();

Of course this Javascript can be improved by getting the margin/ box width values on the fly, but you get the idea. If this function is then hooked to init, resize and orientation change events, we’ll end up satisfying all the requirements:

But it’s still oddly unsatisfying.

‘Solution’ 3

Now there is one thing we could do that only uses CSS. If we jig the previous equation around a bit we could calculate the GridWidth using:

let’s use that in our CSS:

.grid{list-style:none;padding:0;width:calc(100%-((100%mod99px)));}

CSS calc() to the rescue!! Unfortunately it comes with a massive caveat. It is not yet well adopted in mobile browsers, especially Android and also the mod operator is only supported in the latest versions of IE.

Thus we fall over this hurdle:

Nr 7: This grid should work on all smartphones and desktop browsers.

Conclusion

We found two solutions, one using only CSS (but with a method that isn’t yet widely adopted) then also with Javascript. There must be another simple way!

Please comment and let me know! :)

Using regular expressions to hack our way towards AMDfication

$
0
0

Badoo’s MobileWeb project started in early 2012. Due to the initial pace of development, coding conventions and modularisation weren’t given priority. Most of the ‘modules’ lived inside a global object. As the project grew it became difficult to maintain and bugs became harder to track down. So after much internal discussion we found an opportunity to convert our codebase to use AMD modules (RequireJS). I’m here to explain how we used the power of regular expressions to speed up our migration process.

The Problem

Below is a snippet of the kind of code we were working with.

varBadoo=Badoo||{};(function(B){// Local references to other modules and their propertiesvarSession=B.Session;varHistory=B.History;varAppView=B.Views.App;varMESSAGES_FOLDER=B.Models.Folders.TYPES.MESSAGES;// App controller initializationvarApp=B.Controllers.App=Badoo.Controller.extend({init:function(){// Initialize some views and controllersvarcontroller=newB.Controllers.Landing();varview=newB.Views.Alert();// Write some cookiesBadoo.Utils.Cookie.set('test',1000*42);}});// Make it instanceablevarinstance;App.getInstance=function(){returninstance||(instance=newApp('app'));};returnApp;})(Badoo);

As you can see, everything was ‘global’, called directly and had no tracking of dependencies. Fortunately, we had a couple of good things going for us:

  1. The project structure was modular (e.g B.Views.Alert was inside Views/Alert)
  2. In most cases each file corresponded with its object name.

Manual Attempts

Initially we attempted to do this manually, but this had the following drawbacks:

  1. Merge Conflicts - We can’t stop adding features to the original project, so merge conflicts every time there is an update.
  2. Bugs - The process was error prone. Humans are bad at repetitive tasks and we often missed objects or made typos which made debugging frustrating, because a huge chunk of the project needed to be converted before we could boot up the app.
  3. Time - It took us time to convert each file, and we had 400+ files to do.

I realised that this is something that must be automated. First I tried to experiment with Esprima, but that turned out to be time-consuming and reminded me of this XKCD comic:

XKCD

What I needed was a quick solution that semi-automated the process.

Enter Regex

Because most of our project followed predictable conventions, I could try implementing something which matches those patterns and generates AMDfied versions. One of the really cool features in JavaScript is that you can pass methods to Regex replace functions.

varsentence='I like turtles';varwordCount=0;console.log(sentence.replace(/\w+/gi,function(word){returnword+':'+++wordCount;}));// Output: I:1 like:2 turtles:3

So I set out to make a tool which does the following:

  1. Uses CodeMirror as the editor.
  2. Auto-AMDfies the project and lets us know of any errors using JSHint.
  3. Requires minors tweaks to get the pasteable result.

The Solution

And I’m happy to present the solution!

When you press ‘Convert’, it reads the contents of the file, applies the JavaScript below and writes the result. It does leave some code that needs to be removed manually. I could have fixed that as well, but remembering the XKCD chart I decided it’s faster to delete those bits by hand than remove them via code.

Note:If the demo doesn’t work you can visit it by clicking me.

Breakdown of the solution

First we need to have a method which lets us generate define() blocks, given the dependencies.

/** * Generates a define block, formatting it and sorting it based on properties * @return {String} */getBlock:function(){vardefBlock='define([';vari;// Sort all the defines, because why not?// this.define_ is a path:name mappingvardefines=_.values(this.define_).sort(function(a,b){if(a[0]<b[0]){return-1;}if(a[0]>b[0]){return1;}return0;});// Add the indented define pathsvarspaces='';for(i=0;i<defines.length;i++){defBlock+=spaces+"'"+defines[i][0]+"',\n";if(i===0){spaces='        ';}}// Add the define function arguments blockdefBlock=defBlock.slice(0,-2)+'],\n\nfunction (';for(i=0;i<defines.length;i++){defBlock+=""+defines[i][1]+", ";}defBlock=defBlock.slice(0,-2)+') {\n\n';returndefBlock;}

Fairly straightforward, so next we need an array mapping search terms to their replacements.

varrules=[// Basic search replace with empty string['var Badoo = Badoo || {};',''],['})(Badoo);','});'],// Definition// Converts `B.Views.Alert =` to `var AlertView =`[/B\.(View|Model|Controller)s\.(\w+)( )?=/g,function(str,type,file){return'var '+file+' =';}],// MVC// Converts `B.Controllers.XYZ` to `XYZController` and adds a required module[/B\.(View|Model|Controller)s\.(\w+)/g,function(str,type,file){varvarName=file.indexOf(type)===-1?file+type:file;defineHelper.add(type+'s/'+file,varName);returnvarName;}],// Core stuff// Matches and saves a required module[/B\.(View|UI|Session|Router|Model|History|GlobalEvents|Events|Controller|Api)/g,function(str,match){defineHelper.add('Core/'+match,match);returnmatch;}]// and so on...];

So that makes up all the rules we need to follow for a global search/replace.

Note: The order of these things is important (e.g. replace B.Views first followed by B.View)

And now for the magic:

for(i=0;i<rules.length;i++){scriptContent=scriptContent.replace(rules[i][0],rules[i][1]);}

This will iterate over the script, doing replaces one by one. Then we concatenate it with defineHelper.getBlock() to get the result.

Conclusion

It took me a day to code up this tool and it made our conversion process an order of magnitude faster. Using this we migrated two projects and their unit tests within a few weeks. It now allows us to have proper modules in the code, manage circular dependencies and generate subsets of the application. This all adds up to make development much easier.

If you have any feedback please drop it in the comments below.

iOS Code Injection

$
0
0

Code injection is a powerful tool to modify compiled applications at run-time. It’s possible to do in iOS and it can help in many situations. It’s not such a widespread technique so I want to talk about it and give some tips on how to use it.

Wikipedia’s definition:

Code injection is the exploitation of a computer bug that is caused by processing invalid data

Sounds very daunting and hacky. And it is in many ways. But it can be a great tool that can save developer time.

When used as a tool instead of as an exploit, we will be triggering successive recompiles and injecting those into our running app. Then client code inside the app will update the classes and methods, taking advantage of Objective-C runtime.

Why code injection?

In iOS we have to deal with recompile-launch-navigate cycles every time the developer makes a small change and wants to see its effects. There are times where this cycle is repeated over and over, especially when prototyping, tweaking UI values, or adjusting design to spec.

We can break this cycle by using code injection and reloading the affected code without even removing the simulator from the developer screen.

Tools for iOS

Currently there are two open source tools available:

Injection for xcode has more features and works on simulator. Dyci on the other hand is more focused, only offering code and resources injection over to the simulator. Dyci is my tool of choice, given that the library is simpler and allows for an IDE-agnostic setup.

Use cases

  • Prototyping: You can sit with the designer and experiment as you exchange ideas, instead of going back and forth with feedback loops
  • Visual debugging: Some visual bugs are hard to find. You can play around with the view hierarchy and change background colors, or even add debug views
  • Pixel perfect tweaks: This is my favorite. Pair it with a tool like Uberlayer and you can tweak iteratively till your application perfectly matches your design. All this with your simulator always present on screen
  • Small UI changes: Positions and colors? A matter of seconds instead of minutes
  • If you use dyci, pair it with a file watcher tool and you have an IDE agnostic setup
  • Link against the code injection library only in debug configuration, you don’t want this in users’ devices

Conclusion

Using code injection can be very good for small changes, prototyping and implementing pixel perfect designs. Every iOS developer should consider using it.

I recommend using dyci and setting up an IDE-agnostic setup, as described here in more detail.

Code signing and distributing - Swift

$
0
0

At Badoo we’re slowly adopting the new programming language Apple introduced last year: Swift. Even though we don’t plan to write Swift-only apps for now, we do want to write isolated modules and learn while we deliver on our products.

Much has been written about the new language and tools, the buggy release and possible performance problems. What haven’t been mentioned so much are the bumps you may encounter as an iOS developer when distributing an application using Swift. This post intends to sum up the issues we encountered and solutions for other developers facing the same problems.

TL;DR

Be aware of these possible problems:

  • If you are signing your application bundle after it is signed by a standard build, you may need to update your scripts. Your app embeds the swift system libraries it uses, and those need to be signed independently.
  • If you are distributing your applications using Enterprise certificates, check that they have the organizational unit field present. If they don’t, then you need to revoke the certificate and provisioning profile and generate them again.
  • If you build your application from CLI, your upload may be rejected, because of a change in how the IPA is structured. The workaround needs manual scripting on your side.

Code signing

iOS applications are bundles - folders with .app extension - which contain among other things your assets, and most importantly the executable binary.

When a developer builds for a target other than iOS Simulator, the bundle and its contents are digitally signed using a process called code signing. Code signing is a complex topic, but suffice to say that when you build an application, XCode calls the tool codesign from ‘/usr/bin/codesign’. Building an iOS application with Swift code, XCode and xcodebuild will already do the heavy lifting for you, and you generally don’t need to worry about it.

Mostly any change you do to the bundle or embedded content, will result in the signature being different and the application getting rejected on the OS at run time. This can either be by refusing to install the application, or crashing when loading it. If you want to know more, read Apple’s technote.

Embedded frameworks

After iOS8 and Swift, if your application contains Swift code, it embeds the system frameworks it uses inside your application. That means that inside your .app folder, in iOS there will be a folder called ’Frameworks’. That folder will contain the runtime libraries your application uses from Swift.

That folder will also contain any 3rd party frameworks you include with your application.

Re-signing your application

As I mentioned, the Xcode toolchain generally does all the heavy lifting when code signing, but you may encounter a problem if you do modify the application after it is built and signed by xcodebuild.

If you don’t change your scripts you may encounter crashes when running your application in the device. Examples herehere and here.

We’re re-signing our applications before distributing enterprise builds, and we had to update our scripts to take into account signing embedded frameworks before signing the application:

# Re-signing embeded dylibsif[ -e Payload/*app/Frameworks ]thenpushd Payload/*app/Frameworksecho"Copying and re-signing embedded swift libs..."SWIFT_LIBS=`find . -name "*dylib"`SDK_PATH="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/"for dylib in $SWIFT_LIBSdo
        rm "${dylib}"
        cp -v "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/${dylib}" .
        /usr/bin/codesign -f -s "$CERT_NAME""${dylib}"donepopdfi

    /usr/bin/codesign -f -s "$CERT_NAME" Payload/*.app
    zip -qr "$IPA_OUT" Payload

Enterprise certificates

Correctly re-signing your application and embedded frameworks may not be enough. We distribute our applications by signing them with an enterprise certificate. When using this certificate and doing the correct signing as described above, the signed frameworks may be rejected by the OS with a crash.

It seems that enterprise certificates used to sign embedded runtime libraries, need to contain the Organizational Unit field. This was a change possibly introduced around when iOS8 and latest XCode Betas were released. If your certificate was generated before this time, it may not contain that field. This will cause a crash when loading your application in the OS.

The solution is to revoke both the certificate and provisioning profiles. When you generate them again, the certificate will have that field and the signature will be accepted by the OS.

IPA changes

As I have mentioned above, when the application has Swift code, it will embed the runtime libraries into the .app folder. There is another change since iOS8, and that involves how the IPA is structured.

The IPA will also include an additional folder, so your archive will now contain two folders - Payload and SwiftSupport.

This new SwiftSupport folder is generated and added if you build your application from XCode. If you are building your application from command line using xcodebuild to package it, then the created IPA file will not contain that folder. Thus submitting to Apple will result in a rejection with a message:

“Invalid Swift Support - The bundle contains an invalid implementation of Swift. The app may have been built or signed with non-compliant or pre-release tools.”

As of XCode 6.1.1 and XCode 6 Beta4, this problem is always reproducible. See discussion in Apple developer forums, an existing bug report, and our bug report.

Work around

So how do you work around this? The solution is simple; Just package the libraries yourself after the archive has been submitted. We happen to use shenzhen to build and archive that project, so this has been fixed already in a pull request. See the example of how it is implemented in shenzhen. Another example of how to do that with a script here.

Conclusion

We hope that these clarifications serve as a reference for anybody encountering these problems when distributing applications.

Thank you for reading!

Code coverage at Badoo

$
0
0

This article was written over a year ago and since then code coverage estimation has continued to evolve. However the basic approach described in this article has become the standard, which has allowed us to almost double the number of tests without much hassle. Now there are almost 50,000 tests and compile time is nine hours. We’re constantly working to speed up the code coverage calculation process, and plan to release the next set of fixes in the near future.




Speed Some time ago we accelerated code coverage report generation from 70 hours to 2.5 hours. This was implemented as an additional exporting/importing format for the code coverage report. And not long ago our pull requests made it into the official phpunit, phpcov, and php-code-coverage repositories.

At conferences and in articles, we have repeatedly related that we grind through thousands of unit tests in a short period of time. The effect is mainly achieved, as you might guess, through multithreading. That could be the end of the story, but code coverage is one of the most important test metrics.

Today we’ll discuss how to compute and aggregate test code coverage results in a multithreaded environment - and how to do it quickly. Without our optimizations, calculating code coverage took more than 70 hours just for the unit tests. After our optimizations, we now only spend 2.5 hours to run code coverage across all unit tests and two sets of integration tests, more than 30,000 tests in all.

At Badoo, we write tests in PHP and use the PHPUnit Framework devised by Sebastian Bergmann. In this framework, as in many others, coverage is computed through simple calls with the help of the Xdebug extension:

xdebug_start_code_coverage();
//… code is executed here …
$codeCoverage= xdebug_get_code_coverage();
xdebug_stop_code_coverage();

The output is a nested array that contains the files executed during the coverage run, plus line numbers with special flags that indicate whether the code was executed, and whether it should have been executed at all. You can read more about code coverage analysis using Xdebug on the project’s website.

Sebastian Bergmann created the PHP_CodeCoverage library, which is responsible for collecting, processing, and displaying code coverage results in various formats. The library is convenient, extensible, and satisfies us completely. It has a command-line-based front end called phpcov.

But the PHPUnit calls themselves also already include the ability to compute and display code coverage in various formats:

--coverage-clover <file>  Generate code coverage report in Clover XML format.
--coverage-html <dir>     Generate code coverage report in HTML format.
--coverage-php <file>    Serialize PHP_CodeCoverage object to file.
--coverage-text=<file>  Generate code coverage report in text format.

The –coverage-php option is what we need for a multi-threaded launch: each thread calculates code coverage and exports the results to a separate *.cov file. Calling phpcov with the –merge flag aggregates and displays results in a beautiful HTML report.

--merge                 Merges PHP_CodeCoverage objects stored in .cov files.

Everything is displayed an orderly and attractive way and should work “out of the box”. However, it is obvious that not everyone uses this mechanism, including the library’s own author. Otherwise, the “non-optimal” nature of PHP_CodeCoverage’s import/export mechanism would have quickly come to the surface. Let’s take this in stages to figure out what’s going on.

Exporting to the *.cov format is handled by the special PHP_CodeCoverage_Report_PHP class, which has a very simple interface. It consists of a process() method, which takes a PHP_CodeCoverage object and serializes it using the serialize() function.

The result is written to a file (if a path to a file was passed in) or returned by the method.

class PHP_CodeCoverage_Report_PHP{
    /**
     * @param  PHP_CodeCoverage $coverage
     * @param  string           $target
     * @return string
     */
    public function process(PHP_CodeCoverage $coverage, $target= NULL){$coverage= serialize($coverage);if($target !== NULL){return file_put_contents($target, $coverage);}else{return$coverage;}}}

Conversely, phpcov’s import utility takes all of the files with a *.cov extension in a directory and unserializes them into an object. The object is then passed to the merge() method of the PHP_CodeCoverage object where the code coverage results are being aggregated.

protected function execute(InputInterface $input, OutputInterface $output){$coverage= new PHP_CodeCoverage;$finder= new FinderFacade(
            array($input->getArgument('directory')), array(), array('*.cov'));

        foreach ($finder->findFiles() as $file){$coverage->merge(unserialize(file_get_contents($file)));}$this->handleReports($coverage, $input, $output);}

The merge process itself is very simple. It is an array_merge() with minor nuances, such as ignoring what has already been imported or what was passed as a filter parameter to the call to phpcov (–blacklist and –whitelist).

/**
     * Merges the data from another instance of PHP_CodeCoverage.
     *
     * @param PHP_CodeCoverage $that
     */
    public function merge(PHP_CodeCoverage $that){
        foreach ($that->data as $file=> $lines){if(!isset($this->data[$file])){if(!$this->filter->isFiltered($file)){$this->data[$file]=$lines;}continue;}

            foreach ($lines as $line=> $data){if($data !== NULL){if(!isset($this->data[$file][$line])){$this->data[$file][$line]=$data;}else{$this->data[$file][$line]= array_unique(
                          array_merge($this->data[$file][$line], $data));}}}}$this->tests = array_merge($this->tests, $that->getTests());}

It was this approach of using serialization and deserialization that proved problematic in quickly running code coverage. The community has repeatedly discussed the performance of PHP’s serialize and unserialize functions: http://stackoverflow.com/questions/1256949/serialize-a-large-array-in-php;http://habrahabr.ru/post/104069, etc.

For our small project, whose PHP repository contains more than 35,000 files, the coverage files are huge, several hundreds of megabytes each. The final file, which is “merged” from different threads, is nearly two gigabytes. Working on these volumes of data, the unserialize() method was revealed in all its glory. We waited several days for the code coverage results.

So we decided to try the most obvious optimization technique: var_export and include.

We added a new reporter class, which uses var_export to export in a new format, to the php-code-coverage repository:

class PHP_CodeCoverage_Report_PHPSmart{
    /**
     * @param  PHP_CodeCoverage $coverage
     * @param  string           $target
     * @return string
     */
    public function process(PHP_CodeCoverage $coverage, $target= NULL){$output='<?php $filter = new PHP_CodeCoverage_Filter();'
            . '$filter->setBlacklistedFiles(' . var_export($coverage->filter()->getBlacklistedFiles(), 1) . ');'
            . '$filter->setWhitelistedFiles(' . var_export($coverage->filter()->getWhitelistedFiles(), 1) . ');'
            . '$object = new PHP_CodeCoverage(new PHP_CodeCoverage_Driver_Xdebug(), $filter); $object->setData('
            . var_export($coverage->getData(), 1) . '); $object->setTests('
            . var_export($coverage->getTests(), 1) . '); return $object;';if($target !== NULL){return file_put_contents($target, $output);}else{return$output;}}}

We humbly named the file format “PHPSmart”. These files have the *.smart file extension.

To allow the PHP_CodeCoverage class to import and export in the new format, setters and getters were added for its properties.

Then after a few changes to the phpunit and phpcov repositories to adapt them to work with the class, our code coverage runs took only two and a half hours.

This is what import looks like:

foreach ($finder->findFiles() as $file){$extension= pathinfo($file, PATHINFO_EXTENSION);
        switch ($extension){case'smart':$object= include($file);$coverage->merge($object);unset($object);break;
            default:$coverage->merge(unserialize(file_get_contents($file)));}}

You can find our changes on GitHub and try this approach in your own projects.github.com/uyga/php-code-coveragegithub.com/uyga/phpcovgithub.com/uyga/phpunit

We sent Sebastian Bergmann pull requests for our changes, hoping they would soon appear in the author’s official repositories. github.com/sebastianbergmann/phpunit/pull/988github.com/sebastianbergmann/phpcov/pull/7github.com/sebastianbergmann/php-code-coverage/pull/185

But he closed them, saying he didn’t want another format - he wants our format instead of his own: Sebastian Bergmann

We were happy to oblige. And now our changes have become part of the author’s official repositories, replacing the format previously used in *.cov files. github.com/sebastianbergmann/php-code-coverage/pull/186github.com/sebastianbergmann/phpcov/pull/8github.com/sebastianbergmann/phpunit/pull/989

This small optimization helped us accelerate our code coverage by a factor of nearly 30! It allowed us not only to drive unit tests for code coverage, but also to add two sets of integration tests. This did not substantially affect the time spent on importing, exporting, and merging results.

P.S.:Thank you

Is it easy to teach a robot to pass a programming test?

$
0
0

This article will teach you how to write a test-passing robot and will stretch your brain a bit regarding probabilistic theory, as we explore why, in the face of the tasks’ apparent complexity, an automatic brute force attack quickly arrives at a solution. Warning: half the article is maths.

Introduction

Robot

Several years ago I created a test for programmers. Test is in Russian for backend developers positions in our Moscow office. Unfortunately the test is down rignt now, so I don’t put any link here. Anyway most folks probably won’t like the test. If you code in PHP, your favourite DBMS is MySQL, and your preferred operating system is Linux, then you should give it a try. The test was unique, only a few percent of testees were able to successfully pass. The test was honed for specific skills that were not widely needed. Getting an A was hard. That’s why some testees resorted to the dark side - they wrote a bot. And good for them, by the way.

“Persistence and bravery, valour and good fortune. Don’t lose your head when you’re in trouble - That’s what matters most!” - The Adventures of Baron Munchausen.

That’s why the test hasn’t had a captcha. Ever. I actually wanted people to write bots. I wanted the bots to come. I wanted the test to withstand them, to break them. I didn’t want bot writers to cheat, but rather to learn.

The test consists of 80 questions, with 25 chosen at random for each testee. My rationale, which later proved to be utterly invalid, was simple. To make the test impossible to pass by memorizing or brute forcing answers, the total number of possible questions must be initially significantly larger than the number of questions in a single instance of the test. The total number of possible combinations is roughly 1020.

“That’s such a big number,” I thought, “Brute forcing the answers will be very difficult”. Of course that number of combinations is a very crude estimate. But if an automatic brute force attack seemed doable to me, I didn’t think a bot writer would make the necessary effort. I was wrong to think that way. I lost the battle with the bots. I’ll explain why below.

Attacks

Of course, I don’t remember all of the attacks now. Two of them were successful. We’ll talk about those.

The first successful attack was trivial (aside from a million HTTP requests, but that’s nothing). This attack was the result of my stupid mistake. It was perpetrated by my friend Igor Sh. One day I saw his name in first place in the ranking. His score was super high and it had taken him roughly five seconds to complete the test. I called him and asked how he had done it.

At first, Igor had tried to solve the problem by brute force. As I mentioned above, this guy was extremely persistent. There were about a million requests in the log. But the task is more difficult than it seemed at the outset - it took too long to brute force it. Then he suddenly found a “hole”. It turned out that an intentionally incorrect answer could be given; it would be accepted, counted as incorrect, and the test would move on to the next question. This made it possible to brute force the answer to a specific question by giving intentionally incorrect answers to all the other questions. The majority of the permutations are nullified and the task becomes elementary.

Igor’s bot was the first to pass the test. I fixed the bug and had to delete Igor from the ranking. All the remaining attacks weren’t distinguished by their success. Several boring years passed before Semen K. appeared.

This was the second and most recent serious attack. After this, I was humbled and added a captcha. What’s more, it was an excellent mental workout that prompted me to write this post. But I’m getting ahead of myself.

In the middle of November 2013, two hundred and fifty thousand requests arrived from some Swiss host. By the second day, the bot conspicuously held the top places in the ranking and was continuing onward. 250,000 requests, approximately 10,000 instances of the test ― and it was in first place. I was completely bewildered. How did it do that so fast? There were 1020 permutations, but the attack converged much faster! Did I have another bug? If not, then how could I be so wrong in my calculations?

The bot left its creator’s address, and there was nothing left for me to do but admit defeat and write to him. Over the course of the following day, I figured out how the bot worked.

It wasn’t exploiting any holes, rather it worked adaptively: based on results, it improved hypotheses regarding the probability of an answer, rejecting the worst, constantly adjusting solutions, and reducing the search space. The bot’s author would also occasionally edit answers manually by marking new answers that were definitely correct or incorrect, significantly simplifying convergence.

I no longer doubted that I would have to add a captcha. But how could I so severely underestimate an adaptive brute force attack? How was such convergence possible with only 10,000 requests? Basically, it was time to grab a pen and some paper, and think.

Calculating convergence

Now here’s the maths. We want a rough estimate, so we will limit ourselves to loose calculations. Our task is to show that a bot will find a solution in far fewer attempts than a “brute force” calculation across the total number of unique combinations (we’re aware of at least one solution that converged after 104 attempts).

To begin, we’ll use a technique suggested by my colleague from the London office, Evgeniy Kuchera. He proposed looking at the problem like solving a system of random linear equations. Each instance of the test gives an equation in the form of “the sum of these answers to these questions is equal to such-and-such a result”. Each instance of the test gives an additional equation. For simplicity, we’ll assume that each question has five possible answers. All of the equations are linear and the system can have a solution if the number of independent equations is equal to the number of unknowns. The number of unknowns is, roughly speaking, the number of questions multiplied by the number of possible answers: N = 80 * 5 = 400. But the equations’ required dependence is a subtlety that you may be familiar with from a course in linear algebra. You can’t just get the first N equations and assume that the system has a solution: one equation might be a linear combination of other equations and not provide any additional information. But we’ll be tricky and simply show on our fingers that over roughly N tests you won’t get a system of independent equations. You have to really try here.

In fact, once the number of test-instances M exceeds N (= 400), the number of possible combinations of equations grows as the number of combinations “N choose M”, i.e. unbelievably fast. With M = 2*N, this number is already (2*400)!/(400!)2. And that’s a very large number. Without special gimmicks, it can’t even be computed using normal 64-bit double-precision float type, due to overflow. It is more than 10+308. Moreover, the degree to which questions are randomized is also very high: the probability of encountering any pair of questions on the same test is small, roughly (25/80)2 = 0.0977, but given M = 2N test-instances the probability of not encountering this pair on a single test is (1 - 0.0977)2*400 = 10-36! Thus, among M>2N test-instances it is highly likely that we will get N equations such that 1) all of the variables are present and 2) the system is independent. Stronger evidence can be found by analysing the determinant of a random square matrix of zeros and ones, but that sort of mathematical exercise is beyond the scope of this article. I’m also just not confident that I’m in a condition to properly complete that intellectual journey.

In the end, it seems that you can get a system of independent equations from a number of test instances comparable to the number of variables. Even if we’ve made an error somewhere, even by an order of magnitude, this calculation gives an incredible result: we didn’t get an outrageously large number of combinations, but we got almost instantaneous linear convergence relative to the number of questions.

Of course, the reasoning above is rather primitive. Let’s consider another approach that is far more rigorous, visual and very beautiful. This approach undoubtedly has some name and is used widely somewhere, but in my ignorance I don’t know it.

The essence of the approach is this: the bot responds entirely randomly, but only counts tests that yielded a certain number of points. In order for the method to work, the specified number of points must be greater than the most likely number of points (actually, it can be less as long as it is noticeably different). A weight is assigned to each selected answer. This serves as a ranking for the hypotheses regarding the correctness of possible answers. In time, the correct answers receive a high ranking, while the incorrect answers are ranked low. Correct answers can then be easily distinguished from incorrect answers. It sounds strange and is indeed hard to believe at first. We’ll show in detail how this works.

Let’s look at the probability of getting a certain number of points, s, in a test consisting of n questions - p (s, n). For simplicity, we will assume that each question has an identical number of answers, m, and that all answers are random. In this case, the probability of guessing an answer (getting a one) is P(1) = 1/m, while the probability of an incorrect answer (getting an zero) is P(0) = (m-1)/m. The unknown probability of getting s points is something like the number of combinations “s choose n”, and with some multipliers like P(1) raised to the power of s and P(0) raised to the power n-s (we multiply the probability of getting a one s times and a zero n-s times by the total number of combinations). Without yet tiring you with formulas, we present a probability distribution for n = 24 (below we explain why 24 rather than 25):

probability distribution for n = 24

Now we’ll closely scrutinize the following expression:

p (s, n) = p (s-1, n-1) * P(1) + p (s, n-1) * P(0)(1)

The expression’s physical meaning is this: the probability of getting s points in a test with n questions is equal to the sum of the probabilities of two events:

  • in the previous (n-1)th step, we got s-1 points and then got a one
  • in the previous (n-1)th step, we got s points and then got a zero

And now for the most interesting part. Remember that our bot works as follows:

  • the bot considers only tests that yield s points
  • the bot adds +1 to the ranking of each answer in these tests

Thus, each time we’ll increase the ranking of both correct and incorrect answers; and the bot’s behaviour seems illogical at first glance. But let’s look again at (1) and ask ourselves what the probability is that we’ll increase the ranking of an answer that really is correct? Let’s assume that the probability of getting a one is higher than the probability of getting each of the zeros separately (for now we’ll simply make the assumption; we’ll prove it below). Then, in time, from one test to another, the ranking of an answer that is really correct will grow faster than that of an incorrect answer. Given a sufficient number of tests, the correct answer will accumulate a considerable ranking relative to the other possible answers, and it will be easy to distinguish from the incorrect answers.

So, for the method to work we need s such that the probability of getting a one is “noticeably” greater than the probability of getting one of the zeros:

p (s-1, n-1) > p (s, n-1)(2)

Let’s take another look at the distribution (now it should be clear why it’s a distribution for n - 1 = 24 rather than n = 25). Obviously, the unknown s are located to the right of the distribution’s maximum s = 5. Interestingly, the reverse condition is satisfied to the left of the maximum: the probability of getting a one is noticeably less, so when calculating the ranking for tests with such a number of points the correct answer’s ranking will be noticeably less and the correct answer will be easy to distinguish from the incorrect answers.

Thus, the bot may record the sum of points to the left of the distribution’s maximum, i.e. for s = 6. If we come across each possible answer at least a few dozen times, then the correct answer’s ranking will differ noticeably from that of the incorrect answers. Of course, this is again not a rigorous calculation, but I don’t want to bore you with statistical error calculations. We’ll assume that after several dozen tests, the error is negligible. Now let’s estimate the number of tests required to identify correct answers with sufficient accuracy.

To do this, we’ll nonetheless have to write a formula for the probability of an answer for exactly s points in a series of n questions, where each question has m possible answers. The number of test combinations giving s points is the product of “s choose n” (C(n, s)) combinations of questions for which we will guess an answer and the number of incorrect options in the remaining positions (m-1) n - s, given that m-1 incorrect answers and n-s positions remain for each question. The total number of combinations is m n. The ratio of the number of suitable combinations to the total number is the unknown probability:

p (s, n, m) = n!/(s!(n-s)!) * (m-1) n-s/m n(3)

The attentive reader can verify this formula another way: the probability of encountering s ones, P(1) s = m -s, and n-s zeros, P(0) n — s = ((m-1)/m) n — s, multiplied by the total number of combinations “n choose s”.

Let’s return to calculating the convergence. Suppose that we settle on s = 6 for the number of points. According to formula (3), the probability of getting 6 points on a test consisting of 25 five-choice questions is 0.163. Accordingly, getting one “suitable” test would require running the test approximately 1/0.163 = 6 times. Each possible answer must be encountered several dozen times, let’s say 30. Then each question must be encountered 5*30 = 150 times. The probability of encountering a specific question in a test is 25/80 = 1/3.2, which means that 6*150* 3.2 =~ 3000 tests are required to search all of the answers!

To completely convince you that this isn’t some sleight of hand and that a solution really can be found rapidly, we’ll present the results of a numerical experiment. Below we show the growth of the ranking of one of the questions during a brute force attack. As you can see, with only 5000 iterations the correct answer’s ranking significantly outstrips the rankings of incorrect answers. After 10 4 iterations the difference is abundantly noticeable.

rankings of correct and incorrect answers

Conclusions

I’m convinced that writing the bot isn’t difficult. All of the approaches we’ve considered have excellent convergence. Let’s return to the light side of the Force and ask ourselves how we can make life more difficult for bot creators?

Part of the answer is readily apparent. First of all, a captcha should be added, of course, or we should protect the test by sending a text message, e.g. make taking the test reasonably expensive and making automation of the process an uneconomical endeavour.

Secondly, the total set of answers needs to be expanded. It’s true that we converge on a solution linearly. And for a bot the computational complexity required for 100 questions or 1000 questions won’t differ greatly, and creating 1000 questions is a lot of work. Nevertheless, the set of questions should be as big as possible. My test set is constantly growing, and you can help for a fee (I’ll clarify all the details in a personal message).

Only one measure seems to be meaningful: give the testee the “vaguest” possible feedback, since feedback is what bots exploit. For example, don’t report the exact number of points earned. This will impede convergence of an adaptive solution and make it impossible to conduct brute force attack by solving a system of linear equations.

There’s one complication: an imprecise result contradicts common sense. Suppose we split the results into levels, for example, ranging from “Novice” to “Expert”. We can’t have two or three of these levels; we need at least five. Otherwise, why would users take a test that produces utterly imprecise results? But even with answers given as levels an adaptive bot can still converge. This is due to the fact that an adaptive algorithm works well not only on some exact number of points but also on an interval like s > const, where const is the most likely number of points (the distribution’s maximum). The probability decreases monotonically over the entire interval and, as was proven above, this is a sufficient condition for an adaptive algorithm to converge. There remains only one thing to do: select the division of levels such that there are enough of them, on the one hand, for the test to appear sane and, on the other hand, for an entirely automated adaptive transition from one level to another to be virtually impossible.

Recall the distribution for the example with 25 five-choice questions: the probability of a correct random answer to 12 or more questions is insignificant. What if we make the first level start at 12 points or even higher? Then all the most likely tests will produce only one answer: “failed”. And a bot with a “cold engine” won’t run. If we make the next level boundary sufficiently far away, then worming from one level to the next is only possible by further division of the possible answers into “probably correct” and “probably incorrect”, e.g. the bot creator must help their bot and learn something in the process. But that’s not bad per se.

Those are all my conclusions. If you have other ideas on how to protect tests from bots, I’d love to see them in the comments.

Alexey Rybak, Badoo

Better Memory Dumps on Android

$
0
0

This is a follow-up on the previous article about Deobfuscating HPROF memory dumps. Reading that article isn’t a requirement, some knowledge of the HPROF file format is useful.

While implementing the previously mentioned HPROF deobfuscator I became familiar enough with the HPROF file format to realize that there are several aspects of it that could be optimized for usage on mobile devices.

The HPROF format has been around since at least the late 1990s (earliest mention I could find was a bug report from 1998) and since then it has not changed much. Even when Google adopted it as the standard memory dump format for Android they only made some minor additions (requiring the use of hprof-conv to convert to standard HPROF format).

In this article I will make the case for moving to a better (in my opinion) file format and show the benefits and new opportunities that such a move would allow.

TL:DR Show me the code!

The full source code for the Java libraries as well as a working example for Android is available on our GitHub page.

What’s wrong with HPROF?

Inefficient Data Encoding

In the HPROF format, all integers and floating point numbers are stored as fixed length fields (e.g integers take 4 bytes, longs take 8 bytes and so on). For most cases this is very inefficient encoding, and it’s one of the contributors to making HPROF files as large as they are.

By applying the same varint1 (variable length integer) encoding used by protocol buffers2 we can significantly reduce file size. In this encoding format an integer will be stored using 1 to 5 bytes depending on the value, with large values requiring more bytes. Since varints are most efficient for encoding small values (where fewer bytes are needed to represent the value) it would also make sense to replace large values with small ones wherever possible.

One such opportunity involves object and string ids. When debugging a Java application you have probably encountered these in the form of the output of the default toString() method (Object@1434334). The number after the @ symbol is the object id, and as in the example these are often quite high values. If we could remap these ids starting at 1, say, we should once again be able to reduce the file size.

Unused fields

An Android-specific issue with HPROF is that the format contains data that might be relevant to a desktop or server-side Java application:

  • Protection domain ids.
  • Stack trace serial numbers.
  • Signers object ids.
  • Thread serial numbers.
  • Reserved fields (in class dumps).

All of these can be discarded without any side effects (as far as I can see so far) .

Complex file format

As described in the previous article, an HPROF file consists of a file header followed by a number of records. Some of these records (heap dump and heap dump segment) in turn contains sub-records. These sub-records makes parsing the file more complex, but at the same time provide no apparent benefit.

Another issue with the HPROF record structure is that each record starts with a header that contains the size of the record in bytes. This means that you must either first write the full record to an in-memory buffer before creating the header, or perform a first pass where you calculate the record size before writing it. This might not seem like a big deal but consider that some records can take up a significant amount of memory (i.e heap dump records).

Clear text strings

Strings can make up a fairly large part of a HPROF file. For example, a memory dump taken of the Badoo app (while running under normal circumstances) contains about 1.5MB of strings. While these strings could be compressed (e.g using zip) the truth is that most of these strings are not needed in the first place. If you look through the strings you’ll notice that most of them are names of classes, fields, methods or string constants. Of course, these are useful things to have when analyzing the dump but there is no reason why they should be stored in the dump file itself (as long as you have access to the application of which the dump comes from).

Introducing the Badoo Memory Dump (BMD) format

After identifying the problem areas of HPROF I decided to create a new, more efficient file format (see http://xkcd.com/927/). The design goals of the format are first of all to to reduce the file size and then to make sure sure that the files can be read and written using as few system resources as possible (to make it a good fit for mobile devices).

  • Integer and floating point numbers encoded as varints.
  • Flat record structure.
  • No record size in record headers (reduces amount of memory used for buffers).
  • Strings can be either stored in the clear or hashed (reduces size and improves privacy).
  • Primitive arrays replaced with placeholders.
  • Unsupported HPROF records can be repackaged as legacy records.

A full specification of the format can be found on the Badoo github page.

Crunching and decrunching

A new file format is not very useful without a practical application and that is where the two applications HprofCruncher and HprofDecruncher come in. Cruncher converts from HPROF to BMD and is meant to be run on the device where the dump was generated. Depending on the contents of the HPROF file, you can expect it to create a 1-2MB BMD file given a 50MB HPROF file as input.

Due to the ordering of data in an HPROF file the conversion operation is performed in two passes, which means the the source HPROF file is read twice. The problem with this ordering is that an instance dump (containing the instance fields of an object) can come before the class definition of the class that it instantiates. In order to convert the file in one pass you would need to keep an unknown number of instance data records in memory until you have resolved their entire class hierarchy (the worst case would be if all instance dumps are written before the the class definitions, in which case you might need more memory to process the dump than what is available).

First cruncher pass

In the first pass the file header is first read and converted to a BMD header, following this each record is read and processed. At the end of this pass we have a BMD file containing all strings, class definitions and any HPROF fields that we do not support converting (wrapped as LEGACY_HPROF_RECORDs).

  • STRING records are hashed and written to the output file as a BMD STRING record.
  • LOAD_CLASS records are read but nothing is written since there is no matching record in BMD.
  • CLASS_DUMP records are read, an updated class created with some fields discarded and a BMD CLASS DEFINITION record is written. The class definition is then kept in memory to be used in the second pass.
  • All other records which are not sub-records to a HEAP_DUMP or HEAP_DUMP_SEGMENT record are wrapped into LEGACY_HPROF_RECORD records before being written.

Second cruncher pass

The second cruncher pass is simpler than the first. In this pass we are only concerned with sub-records of HEAP_DUMP and HEAP_DUMP_SEGMENT records. At the end of the second pass we have a complete BMD file.

  • INSTANCE_DUMPs are read, updated based on the class definition from the first pass and then written as BMD INSTANCE_DUMP records.
  • OBJECT_ARRAY records are read and converted to BMD OBJECT_ARRAY records.
  • PRIMITIVE_ARRAYS are read, their contents stripped and then written as PRIMITIVE_ARRAY_PLACEHOLDER records.
  • All root object records (e.g ROOT_STICKY_CLASS, ROOT_JAVA_FRAME, etc) are collected and written as one ROOT_OBJECTS record.

Cruncher optimizations

Besides converting to BMD, cruncher also performs several other optimizations:

  • Discard primitive fields (as they are usually not required to analyze out of memory situations).
  • Remap all object and string ids starting at 1 (0 is reserved for null values).
  • Replace most strings with hashes (reduce file size and improves privacy).

As an added benefit, by discarding primitive fields and array data as well as hashing strings we are creating an anonymized memory dump which will come in very handy if we want to upload the file from the device to a server (more on this later).

Decruncher

As the name might hint at, decruncher does the opposite of cruncher, it converts BMD files to HPROF (so that you can load the file into your favorite memory analyzer). Its input is a BMD file but it also accepts .jar and .apk files as a secondary input. These files are needed in order to recreate the hashed strings stored in the BMD file.

Decruncher will create a HPROF file that has similar contents to the original HPROF file but there are some significant differences:

  • Primitive fields are replaced with placeholder fields (not only the field content but also the field type is lost). The replacement fields that are inserted are only there to keep the object size consistent with the original one. This is done to make sure that OutOfMemory issues can be debugged.
  • All strings that were not part of the originally built application will be lost. This means that text entered by the user, or received from an outside source (network communication) are lost and replaced with a placeholder string which contains the string hash value.
  • All root objects are reported as UNKNOWN_ROOT records which means that you cannot know if the object was referenced from the heap, stack or from JNI.

Where to go from here?

Now when memory dumps can be made small enough to transmit over the internet it opens up several new opportunities. The most obvious one is that you could let your application upload a dump file to a server after crashing due to an out of memory situation, similar to how crashes are reported to a service like Crittercism or HockeyApp.

However, it’s when you follow this idea to its next step that things start to get really interesting. How about sending memory dumps not only when your app has run out of memory but also when it has crashed due to some other error? How many bugs could be easily found and fixed if you could inspect the (almost) complete state of the app when the crash occurred?

A further area of exploration is to turn cruncher into a more generic HPROF to BMD converter, one where you can decide what information is kept and what is discarded. In its current incarnation, the tool is solely focused on generating an output file of minimal size, however this might not always be the desired end-result (especially if you want to use the dump to debug crashes other than Out Of Memory situations).

References

  1. http://en.wikipedia.org/wiki/Variable-length_quantity
  2. http://en.wikipedia.org/wiki/Protocol_Buffers

Testing Android notifications in Calabash

$
0
0

It’s generally understood that Calabash can only operate controls that are part of the application it’s testing, but this limitation is particularly annoying with status bar notifications.

We would like to write something like this at the feature level:

Given I press homeAnd I verify no notification with "You got an award on Badoo!"And the server sends me an "award" notificationWhen I click a notification with "Badoo Award" and "You got an award on Badoo!"Then I verify I am on the award screen

We click ‘home’ here with adb shell input keyevent KEYCODE_HOME because Badoo has special handling when notifications arrive while the app is open; your needs may differ.

The step definition is trivial:

When(/^I click a notification with "([^"]*?)"(:? and "([^"]*)")?(?: within (\d+) seconds?)?$/)do|text1,text2,timeout|click_notification_matched_by_full_text((timeout||1).to_f*1000,text1,text2)end

but the implementation is the key

An indirect approach

Even if you could look at and click on the notifications, there is an argument that you should only test things which are under your direct control. If, for instance, the user interface of Android’s notification mechanism changes radically (e.g. phones vs tablets) it will at the least break your tests, and at worst make them impossible to mend.

One indirect route to checking notifications involves adding a special test mode to the application code that generates notifications, to keep a list of notifications instead of posting them and allowing Calabash to retrieve that list. Calabash can then make assertions about the notification data and issue the intents, to ensure that the application responds correctly. Intents don’t care whether they’re launched by a notification being pressed, so you could test them at multiple points in your app without having to re-generate the notification, if it makes sense to do so.

You might object that this isn’t doing a full round-trip through the notification system so something could go wrong, and you’d be strictly correct, but how often do you change the code that sits between generating a notification and posting it? Do you need to test that every time, if the text and intent given to it are checked? Consider this a form of dependency injection - ‘injecting’ a testable notification mechanism.

A direct approach

There is a more direct route to checking notifications that you may find useful, and it’s the one we’re using for now because it suits our immediate needs.

Everyone knows that Calabash isn’t limited to talking to the phone through its server: most solutions to testing notifications suggest examining the output of adb shell dumpsys notifications to see whether the notification is being displayed, but as mentioned above it’s just as important to test that the pressing of a notification ends up in the correct part of the application.

Calabash can invoke adb shell uiautomator dump on Jellybean (4.1) and later devices to retrieve an XML document describing the current display. This isn’t particularly fast as an operation, but most applications only have a few notifications to test, so it’s a reasonable trade-off. It’s possible to combine that view list with adb input swipe to open the notification drawer (on a phone; tablets are more varied) or dismiss a notification, and adb input tap to press the notification (or the action buttons on the notification, if you’re feeling fancy). Again, not the fastest or most elegant solution, but it’s workable.

A particularly nice aspect of uiautomator’s XML is how easy it makes finding a notification with two strings - you only need a simple xpath:

//node[./node/node[@text='Badoo Award']][./node/node[@text='You got an award on Badoo!']]

Code

What follows is our initial code, and should serve as a reasonable starting point.

UIAutomator

Retrieving the UIAutomator dump requires a couple of calls: one to create the dump, and one to get it off the phone. I’m using adb shell cat instead of adb pull because it saves managing temporary files on the host machine.

defuiautomator_dumpstdout,stderr,status=exec_adb('shell uiautomator dump')unless/dumped to: (?<file>\S*)/=~stdoutfail"uiautomator dump failed? Returned #{stdout} :: #{stderr}"endstdout,stderr,status=exec_adb("shell cat #{file}")[stdout,stderr,status]end

We wish to identify a notification by its text strings - it might have one or two. This function builds an xpath for a node whose grandchildren have the given texts - works for us for now, but obviously notification layouts aren’t guaranteed to follow this pattern in future releases.

defxpath_for_full_path_texts(params)texts=params.keys.grep(/^notification.full./)clauses=texts.collect{|k|"./node/node[@text='#{params[k]}']"}"//node[#{clauses.join('][')}]"end

The UIAutomator dump is mostly elements containing other elements, and they all have a 'bounds' attribute giving the left, top, right, and bottom edges of the view rectangles. This method extracts the bounds. (It actually takes the first from a set of nodes, returned from the xpath call.)

defextract_integer_bounds(set)returnnilifset.empty?match=(set.attr('bounds').to_s.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/))match.captures.collect(&:to_i)end

This allows us to run a block of code with the bounds of the first node selected by an xpath - note that the output of uiautomator can involve overlapping views, and it’s not always easy to determine whether a view is effectively clickable. Our more complicated version of this routine clips children to their parents’ rectangles (to account for scrollviews) and checks for potentially overlapping siblings of each ancestor up to the root (we ignore siblings larger than half the size of the parent).

defbounds_from_xpath(xpath)stdout,_stderr,_status=uiautomator_dumpset=Nokogiri::XML(stdout).xpath(xpath)if(bounds=extract_integer_bounds(set))returnyieldboundselsereturnnilendend

Interacting with the phone

We interact with the phone through adb input because it’s easy and portable.

This function opens the notification shutter on most phones by swiping down from the top (needs improving for tablets):

defopen_notification_shutterbounds_from_xpath('//node[1]')do|x1,y1,x2,y2|xm=(x1+x2)>>1exec_adb("shell input swipe #{xm}#{y1}#{xm}#{y2}")endend

This taps a notification (and ensures that the notification shutter is closed)

deftap_notification(xpath)found_bounds=bounds_from_xpath(xpath)do|x1,y1,x2,y2|ym=(y1+y2)>>1exec_adb("shell input tap #{(x1+x2)>>1}#{ym}")enddismissed=!found_bounds.nil?keyboard_enter_keyevent('KEYCODE_BACK')unlessdismissedreturndismissedend

This swipes a notification to dismiss it and then ensures the notification shutter is closed

defdismiss_notification(xpath)found_bounds=bounds_from_xpath(xpath)do|x1,y1,_x2,y2|ym=(y1+y2)>>1exec_adb("shell input swipe #{x1}#{ym} 10000 #{ym}")endfound_bounds.nil?end

Implementing the step

This combines the above code to tap or dismiss a notification, with retry logic if it hasn’t yet been received. The code could be simpler (particularly parameter handling), but we have a Lollipop-specific code path not shown in this article that uses an instrumentation to invoke UiDevice.openNotification() and related Android calls for more reliable testing on tablets.

defhandle_notification(params)xpath=xpath_for_full_path_texts(params)timeout=params['timeout'].to_istart=Time.newwhilestart+timeout/1000>Time.newopen_notification_shutterifparams['action.click']breakiftap_notification(xpath)elsebreakifdismiss_notification(xpath)endendenddefclick_notification_matched_by_full_text(timeout,*strings)h={'timeout'=>timeout.to_s,'action.click'=>'true'}strings.map.with_index{|v,i|h["notification.full.#{i}"]=vifv}handle_notification(h)enddefdismiss_notification_matched_by_full_text(timeout,*strings)h={'timeout'=>timeout.to_s,'action.dismiss'=>'true'}strings.map.with_index{|v,i|h["notification.full.#{i}"]=vifv}handle_notification(h)end

For completeness, the adb function:

defexec_adb(cmd)adb_cmd="#{default_device.adb_command}#{cmd}"stdout,stderr,status=Open3.capture3(adb_cmd)unlessstatus.success?fail"Adb failed: #{adb_cmd} Returned #{stdout} :: #{stderr}"end[stdout,stderr,status]end

Let's build: Freehand Drawing in iOS - Part 1

$
0
0

This post will be the first of a series in which we follow the development of a specific feature. We want to add to the great quantity of existing tutorials on the internet by sharing practical knowledge directly from our engineering team.

This time we will demonstrate the implementation of Freehand Drawing, aka Doodling in iOS.

Structure of the tutorials

These tutorials are aimed at mid-level developers. We will skip the basic project setup and focus on the domain parts of the feature, discussing the reasoning behind some details, and the architecture.

We’ll guide you from a naive or ‘first time’ implementation, through natural iterative improvements. The final implementation we’ll reach will be very close to production-level quality code.

All the code is in a repository, with tags referencing specific milestones.

So all things said, let’s dive into the feature!

Freehand drawing

Let’s say you have an (awesome) application involving some kind of user communication. Wouldn’t it be great if users could send each other doodles or hand-written notes? That’s what we’ll build.

Freehand Drawing lets the user use her finger to draw as if it was a pencil or brush. We want to offer some kind of canvas where she can draw, undo as many times as she wants, and save the result as an image to be sent to somebody. As an example of such a feature, you can look at any doodling app. For example, Kids Doodle, You Doodle, or the notorious Draw Something.

There are many applications that don’t focus on drawing, but offer such functionality as part of the experience, such as photo editing apps. You can check Bumble or Snapchat photo-sending as an example.

Disclaimer: We don’t want to build a fully-featured drawing application like Paper, but rather a simple way for users to draw stuff that can be used for other purposes.

What we will build:

User facing features:

  • Ability to draw lines and points
  • Ability to undo changes
  • Ability to change drawing color
  • Ability to export to an image

Technical details:

  • Maintain performance with lots of lines
  • Build undo functionality
  • Improve stroke appearance from ‘computerish’ to a more realistic hand drawing style

Choice of API

Before going into details about how we’d implement it, on the iOS platform we effectively have two API choices:

In this case the most lightweight and hence first choice for implementation would be direct use of Core Graphics. The reason is twofold: firstly we should always try to use higher level APIs to achieve what we need, as they offer better abstractions and need less code; secondly, using OpenGL, ES requires more setup and higher developer knowledge, but this would be a small part of our application and the performance of Core Graphics is expected to be sufficient.

We will use Swift and XCode 6.4 (in beta at the time of writing this article).

Naive version

This is the first part of the tutorial series, where we’ll build a naive version, analyse what’s wrong and what can be improved.

Give me the code!

All the code for this tutorial series is here

A first thought on how to enable drawing for the user is to utilise Core Graphics in a view.

The simplest initial approach is to create a UIView subclass that handles user touches and constructs a Bezier path with the points the user goes through. Then we’ll redraw every time a new point is added by the user moving their finger. We’ll draw simple straight lines between captured points, and add a round cap to the stroke.

Jump directly to this version here.

The ViewController does nothing at the moment. Let’s focus on the implementation of the DrawView:

importUIKitclassDrawView:UIView{overridefuncawakeFromNib(){super.awakeFromNib()self.setupGestureRecognizers()}// MARK: Drawing a pathoverridefuncdrawRect(rect:CGRect){// 4. Redraw whole rect, ignoring parameter. Please note we always invalidate whole view.letcontext=UIGraphicsGetCurrentContext()self.drawColor.setStroke()self.path.lineWidth=self.drawWidthself.path.lineCapStyle=kCGLineCapRoundself.path.stroke()}// MARK: GesturesprivatefuncsetupGestureRecognizers(){// 1. Set up a pan gesture recognizer to track where user moves fingerletpanRecognizer=UIPanGestureRecognizer(target:self,action:handlePan:)self.addGestureRecognizer(panRecognizer)}@objcprivatefunchandlePan(sender:UIPanGestureRecognizer){letpoint=sender.locationInView(self)switchsender.state{case.Began:self.startAtPoint(point)case.Changed:self.continueAtPoint(point)case.Ended:self.endAtPoint(point)case.Failed:self.endAtPoint(point)default:assert(false,Statenothandled)}}// MARK: Tracing a lineprivatefuncstartAtPoint(point:CGPoint){self.path.moveToPoint(point)}privatefunccontinueAtPoint(point:CGPoint){// 2. Accumulate points as they are reported by the gesture recognizer, in a bezier path objectself.path.addLineToPoint(point)// 3. Trigger a redraw every time a point is added (finger moves)self.setNeedsDisplay()}privatefuncendAtPoint(point:CGPoint){// Nothing to do when ending/cancelling for now}vardrawColor:UIColor=UIColor.blackColor()vardrawWidth:CGFloat=10.0privatevarpath:UIBezierPath=UIBezierPath()}
  1. We set up a gesture recognizer to track the movement of the finger on the screen
  2. We accumulate the points the user has gone through with her finger in a UIBezierPath object.
  3. Every time a new point is added, we invalidate the whole drawing bounds.
  4. Our custom drawRect implementation takes the accumulated path and draws it with the selected color and width. Note that it strokes the whole path every time a new point is added.

This code has a very big problem, which is performance. As it stands now, the more you draw the slower the interface responds, to a point where it becomes unusable.

Another future problem is that user will not be able to draw strokes with different colors, as we use the same color for the whole path. Let’s focus on the performance problem first.

Less naive version: Painter’s algorithm

To address the creeping issue of our first naive implementation, we need to understand why this happens.

Even without profiling, if you analyse what the code is doing, you will notice that we are drawing the whole accumulated path every time a new dragging point is added. The path grows larger with every finger movement, which means we need to do more UI blocking work to draw the path. This will block the touch event processing and thus the perception of lag.

We really don’t need to redraw the whole path every time, because the strokes will always go over what was drawn before, similar how the Painter’s algorithm works. We should keep the work we do between points to a minimum for the UI to be responsive.

A possible optimization is to cache what we have drawn already into an image, and just redraw over that image every time a point is added. The amount of work to do between finger movements will be constant - a line stroke and setting the bitmap to the view.

The full set of changes are here.

First, we don’t need to accumulate a path. We’ll remember the last point so we can build lines for every new point:

privatefuncstartAtPoint(point:CGPoint){self.lastPoint=point}privatefunccontinueAtPoint(point:CGPoint){// 2. Draw the current stroke in an accumulated bitmapself.buffer=self.drawLine(self.lastPoint,b:point,buffer:self.buffer)// 3. Replace the layer contents with the updated imageself.layer.contents=self.buffer?.CGImage??nil// 4. Update last point for next strokeself.lastPoint=point}privatefuncendAtPoint(point:CGPoint){self.lastPoint=CGPointZero}

The new incremental draw routine:

privatefuncdrawLine(a:CGPoint,b:CGPoint,buffer:UIImage?)->UIImage{letsize=self.bounds.size// Initialize a full size image. Opaque because we don’t need to draw over anything. Will be more performant.UIGraphicsBeginImageContextWithOptions(size,true,0)letcontext=UIGraphicsGetCurrentContext()CGContextSetFillColorWithColor(context,self.backgroundColor?.CGColor??UIColor.whiteColor().CGColor)CGContextFillRect(context,self.bounds)// Draw previous buffer firstifletbuffer=buffer{buffer.drawInRect(self.bounds)}// Draw the lineself.drawColor.setStroke()CGContextSetLineWidth(context,self.drawWidth)CGContextSetLineCap(context,kCGLineCapRound)CGContextMoveToPoint(context,a.x,a.y)CGContextAddLineToPoint(context,b.x,b.y)CGContextStrokePath(context)// Grab the updated bufferletimage=UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()returnimage}

This solution is an improvement over the naive drawing, and also allows us to change drawing color for every finger stroke.

Memory problems

Let’s run this code on an older device. Say an iPhone 4S. We expect this code might not be high performance enough, but it is sufficient for our feature on such a low end device.

Now keep running drawing strokes for a while, especially fast strokes. You’ll eventually crash the application. The crash was due to a memory warning. With such small amount of code we now have memory problems! Building applications for mobile we always need to be mindful of memory constraints.

Let’s run profile the code with the allocations instrument. Here is a run I captured reproducing the memory warning:

Memory4S

You can also run the application in XCode and check the memory gauge inside the debugging tab. So what’s going on?

We are allocating a lot of transient images while the user is drawing. There is no memory leak but the drawn images are autoreleased as per ARC rules. The offending line is this one:

letimage=UIGraphicsGetImageFromCurrentImageContext()

The transient images are autoreleased; that is, released and removed from memory at a later time when the runloop finishes its cycle. But in cases where we have many touches accumulated, we keep adding work to the main thread, thus blocking the runloop.

This is a case with lots of transient and costly objects, and we should step in and force ARC to release the images as soon as we are done with them. It’s as simple as wrapping the code with an autorelease pool, to force the release of all autoreleased objects at the end of this method:

privatefunccontinueAtPoint(point:CGPoint){autoreleasepool{// 2. Draw the current stroke in an accumulated bitmapself.buffer=self.drawLine(self.lastPoint,b:point,buffer:self.buffer)// 3. Replace the layer contents with the updated imageself.layer.contents=self.buffer?.CGImage??nil// 4. Update last point for next strokeself.lastPoint=point}}

Why we didn’t experience this problem on an iPhone 6? Look at the allocations trace:

Memory6

Seems similar doesn’t it? The reason is simply that device has more available memory so even if we use more memory, and we don’t reach the OS limit for memory warning. Nevertheless using less memory in mobile is a goal we should be striving for, so this optimisation will only benefit our iPhone 6 users.

Adding a toolbar and changing color

Adding a toolbar and changing the color is only a matter of structure. Our DrawViewController will manage interaction between subviews; the Toolbar and DrawView.

We mention this part because it’s often the case that sample code omits a bit of architecture for the sake of simplicity, but that leads to the false impression that ‘everything goes’ into the ViewController subclass. Massive View Controller is an illness creeping into many iOS codebases. We don’t want to contribute to this illness.

This is how the feature looks:

Result

Check the finished code for this post here.

Analysis

We’ve seen how to implement a simple drawing feature using a custom UIView. We hit a performance problem with very long sets of strokes, and fixed it by caching the previous strokes in an offscreen buffer. We also found and fixed high transient memory usage, which produced a crash on lower end devices.

What about adding the other features?

  • Think about how to add undo to this code.
  • What about adding more gestures such as detecting a tap to draw a point. Will the code be as clean as it is now?
  • The stroke is very simple and does not emulate handwriting in any way. This can be improved and we’ll see some ways to do that in upcoming posts.

In the next post we’ll add undo functionality, and will see how to change our simple code with a better, more extensible design.

Let's build: Freehand Drawing in iOS - Part 2

$
0
0

This the second in a series of tutorials in which we build Freehand Drawing for iOS. In this part we’ll add undo functionality and the ability to draw dots.

In the previous post we built a simple UIView subclass that handles touches and draws the stroke. We found some performance and memory issues and fixed them. Now we’ve reached the point where we want to add more functionality to this simple implementation.

Drawing dots

In the initial implementation we didn’t add a way for the user to draw dots. We can interpret that she wants to draw a dot when she taps on the screen.

Let’s try to add this to the current code, with the simplest implementation we can think of. You can go directly to the changes here.

So the first thing we need to do is add a gesture recognizer for taps, and draw a dot when the gesture is ended:

privatefuncsetupGestureRecognizers(){// Pan gesture recognizer to track linesletpanRecognizer=UIPanGestureRecognizer(target:self,action:"handlePan:")self.addGestureRecognizer(panRecognizer)// Tap gesture recognizer to track pointslettapRecognizer=UITapGestureRecognizer(target:self,action:"handleTap:")self.addGestureRecognizer(tapRecognizer)}@objcprivatefunchandleTap(sender:UITapGestureRecognizer){letpoint=sender.locationInView(self)if(sender.state==.Ended){self.tapAtPoint(point)}}

Now we need to draw a dot. Drawing a dot itself is achieved by drawing a filled circle. But then we duplicate a lot of the functionality and optimizations we previously did when drawing lines. We can refactor to share that code. This is how drawLine and the new drawPoint methods look, with the common code refactored into a utility method:

// MARK: Drawing a pathprivatefuncdrawLine(a:CGPoint,b:CGPoint,buffer:UIImage?)->UIImage{letimage=drawInContext{contextin// Draw the lineself.drawColor.setStroke()CGContextSetLineWidth(context,self.drawWidth)CGContextSetLineCap(context,kCGLineCapRound)CGContextMoveToPoint(context,a.x,a.y)CGContextAddLineToPoint(context,b.x,b.y)CGContextStrokePath(context)}returnimage}// MARK: Drawing a pointprivatefuncdrawPoint(at:CGPoint,buffer:UIImage?)->UIImage{letimage=drawInContext{contextin// Draw the pointself.drawColor.setFill()letcircle=UIBezierPath(arcCenter:at,radius:self.drawWidth/2.0,startAngle:0,endAngle:2*CGFloat(M_PI),clockwise:true)circle.fill()}returnimage}// MARK: General setup to draw. Reusing a buffer and returning a new oneprivatefuncdrawInContext(code:(context:CGContextRef)->Void)->UIImage{letsize=self.bounds.size// Initialize a full size image. Opaque because we don't need to draw over anything. Will be more performant.UIGraphicsBeginImageContextWithOptions(size,true,0)letcontext=UIGraphicsGetCurrentContext()CGContextSetFillColorWithColor(context,self.backgroundColor?.CGColor??UIColor.whiteColor().CGColor)CGContextFillRect(context,self.bounds)// Draw previous buffer firstifletbuffer=buffer{buffer.drawInRect(self.bounds)}// Execute draw codecode(context:context)// Grab updated buffer and return itletimage=UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()returnimage}

And this is how it looks (warning, programmer art ahead):

DrawView-points

This works fine, but as we’ll see in the next section, our approach of adding more code to existing classes is not very flexible.

Undo functionality

The main feature we want to talk about in this post is adding undo. You probably know how it works but let’s review it anyway. We’ll add a button which lets the user to go back in time and delete the last line or point she drew.

This will allow her to correct mistakes or simply change her mind after drawing something. The button can be tapped repeatedly with the effect of deleting more and more lines and dots until the drawing is as empty as it was in the beginning.

Bear in mind that one ‘undo’ will remove the last full line or dot the user drew between touching the screen and lifting the finger from the screen, not the last part of the last line.

Refactoring

The current code is simple and understandable. To add undo, possibly the first idea that comes to mind is to add some kind of memory of the path we have been drawing, and then remove the last part when user taps the button. That could be a simple ordered array of points the user went through.

But what about the fact that we want to undo a whole stroke? This means we need to keep track of when the stroke started and when it ended.

What about dots? We need to differentiate between drawing dots and drawing lines. And then, how do we actually undo a line? We have a cached buffer with the accumulated contents the user has been drawing, and we can only render on top of it.

Another issue is that all the logic and display is in the same object. The only way to access touch data and the history of user finger movements is to add that functionality directly to the view. It seems that our DrawView object code will grow very fast, so it will be harder to understand and maintain in the future.

In this case, we can address the issue by stepping back and changing our design. We’ll use a software design pattern, and this problem in particular is easily modelled with the command pattern.

Command pattern

The command pattern encapsulates the execution of some actions (the command), and abstracts the user of the actions, so it does not know how they are performed. The commands can then be used in a homogeneous way, and higher level operations become trivial, such as reorder, persistence, or in our case, undo.

The commands

Let’s try to model our commands. In our domain the commands are strokes the user draws with her finger. Let’s introduce a protocol for them:

protocolDrawCommand{funcexecute()}

Commands should only do one thing, and that is execute their encapsulated actions. The setup, autorelease pool and offscreen buffer are not the responsibility of the command itself, rather it’s the responsibility of the environment on which our command works. Let’s express this with our protocols:

protocolCanvas{varcontext:CGContext{get}}protocolDrawCommand{funcexecute(canvas:Canvas)}

We can now create our first commands directly from our drawLine and drawPoint methods. To draw a line we need two points, the width, color, and a context from the canvas:

structLineDrawCommand:DrawCommand{leta:CGPointletb:CGPointletwidth:CGFloatletcolor:UIColor// MARK: DrawCommandfuncexecute(canvas:Canvas){CGContextSetStrokeColorWithColor(canvas.context,self.color.CGColor)CGContextSetLineWidth(canvas.context,self.width)CGContextSetLineCap(canvas.context,kCGLineCapRound)CGContextMoveToPoint(canvas.context,a.x,a.y)CGContextAddLineToPoint(canvas.context,b.x,b.y)CGContextStrokePath(canvas.context)}}

Similarly we can construct our command to draw a circle:

structCircleDrawCommand:DrawCommand{letcenter:CGPointletradius:CGFloatletcolor:UIColor// MARK: DrawCommandfuncexecute(canvas:Canvas){CGContextSetFillColorWithColor(canvas.context,self.color.CGColor)CGContextAddArc(canvas.context,self.center.x,self.center.y,self.radius,0,2*CGFloat(M_PI),1)CGContextFillPath(canvas.context)}}

Separating View and Logic

The most important step is to separate the logic of the commands from the view. Our view in this case will act as the environment for the commands to execute on. It will also do all the required performance optimisations, but there will be no logic about gestures or commands.

We can create a different object, a controller if you will, to do all the gesture and command logic. This object doesn’t need to be a UIViewController subclass. In fact it only has to be an NSObject subclass as it needs to offer selectors for callbacks of gesture recognizers. This object will hold an ordered list of commands.

You can find the change here. Let’s highlight the key changes. First we have introduced another protocol, called DrawCommandReceiver, just to decouple the view and controller objects, as executing a command needs the context to be set up in the view:

protocolDrawCommandReceiver{funcexecuteCommand(command:DrawCommand)}

The view contains much less code, conforms to two protocols and has no gesture logic:

classDrawView:UIView,Canvas,DrawCommandReceiver{// MARK: Canvasvarcontext:CGContextRef{returnUIGraphicsGetCurrentContext()}// MARK: DrawCommandReceiverfuncexecuteCommand(command:DrawCommand){autoreleasepool{self.buffer=drawInContext{contextincommand.execute(self)}self.layer.contents=self.buffer?.CGImage??nil}}// Method drawInContext remains the same as before. All other code is removed}

All the logic is moved to a new class, called FreehandDrawingController. It contains the same gesture handling as before, but it delegates the execution of the commands to the canvas:

// MARK: Draw commandsprivatefuncstartAtPoint(point:CGPoint){self.lastPoint=point}privatefunccontinueAtPoint(point:CGPoint){letlineCommand=LineDrawCommand(a:self.lastPoint,b:point,width:self.width,color:self.color)self.canvas.executeCommand(lineCommand)self.commandQueue.append(lineCommand)self.lastPoint=point}privatefuncendAtPoint(point:CGPoint){self.lastPoint=CGPointZero}privatefunctapAtPoint(point:CGPoint){letcircleCommand=CircleDrawCommand(centre:point,radius:self.width/2.0,color:self.color)self.canvas.executeCommand(circleCommand)self.commandQueue.append(circleCommand)}privateletcanvas:protocol<Canvas,DrawCommandReceiver>privatevarcommandQueue:Array<DrawCommand>=[]privatevarlastPoint:CGPoint=CGPointZero

Undo implementation

Now we’ve refactored our code and made it easier to reason about by having a list of commands that are executed in the canvas. The remaining question is how to actually implement an undo.

As you may recall, the way that in Core Graphics all changes are additive. This means that executing any API will draw on top of what was there previously. We also store the drawing in an offscreen image that we are continuously modifying and replacing onscreen.

A way to see undo graphically is as a deletion, but how do we express this in the terms of our API? There are several ways, so let’s consider just two of them:

  • A command knows how to undo itself, by drawing what was there before it was executed.
  • A command only knows how to do itself and undo is implemented at the queue level.

Redoing whole queue

We can think of undo as executing all commands but the last one in order, on an empty canvas. Implementing undo this way makes code and commands simpler. The commands only need to know how to ‘do’ the action, but not how to ‘undo’ it.

The drawback of this solution is that undoing will take longer if the queue has many commands. The slowness is likely not to be perceived by the user as they tap a button. Let’s implement it and see if it will affect the user experience.

We’ll add a button to our toolbar, which will result in a method call of our new FreehandDrawController. The controller will then remove the last command from the queue, clear the canvas and execute all the other commands in order:

// FreeHandDrawController.swiftfuncundo(){ifself.commandQueue.count>0{self.commandQueue.removeLast()self.canvas.reset()self.commandQueue.map{self.canvas.executeCommand($0)}}}

We’ll need a new reset method on our canvas protocol, implemented in the view:

funcreset(){self.buffer=nil}

If you run this, you’ll see that it does not work as expected. There are two problems:

  1. Undo is very slow every tap, even when user draws only a few lines.
  2. We’re deleting the last part of the last line, not the whole line itself. This is not what we expect when we tap the button.

Fixing the performance problem means changing the DrawCommandReceiver protocol to accept an ordered list of commands to execute, as opposed to only one command. This will allow the underlying view to set up the context and change the buffer only once for the whole set of commands.

You can see the previous changes here.

Composed command

To make our undo operation behave as intended, we can take advantage our types, which requires very little effort.

A whole line is constructed by all the intermediate points the user is going through with her finger. We can model this in our controller by using a composed command.

A composed command will be a command that contains an ordered list of commands. These commands will be single line commands. The queue itself will contain either composed commands (a whole line) or a circle command (a dot). That way we’ll still undo the last command in the queue, but actually undo the whole stroke.

Check the changes here.

The composed command:

structComposedCommand:DrawCommand{init(commands:[DrawCommand]){self.commands=commands;}// MARK: DrawCommandfuncexecute(canvas:Canvas){self.commands.map{$0.execute(canvas)}}mutatingfuncaddCommand(command:DrawCommand){self.commands.append(command)}privatevarcommands:[DrawCommand]}

This does not expose the internal array of commands but allows the addition of a new command. Removing a command is not needed for now.

We can use this new command in our controller. We’ll accumulate line commands in a temporary composed command, until the user finishes the stroke gesture. When the gesture is finished we’ll save the composed command in the queue:

// FreehandDrawController.swiftprivatefuncstartAtPoint(point:CGPoint){self.lastPoint=pointself.lineStrokeCommand=ComposedCommand(commands:[])}privatefunccontinueAtPoint(point:CGPoint){letlineCommand=LineDrawCommand(a:self.lastPoint,b:point,width:self.width,color:self.color)self.canvas.executeCommands([lineCommand])self.lineStrokeCommand?.addCommand(lineCommand)self.lastPoint=point}privatefuncendAtPoint(point:CGPoint){ifletlineStrokeCommand=self.lineStrokeCommand{self.commandQueue.append(lineStrokeCommand)}self.lastPoint=CGPointZeroself.lineStrokeCommand=nil}// New var:privatevarlineStrokeCommand:ComposedCommand?

With these changes we’ve finished our undo implementation.

Conclusion

We’ve added the ability to draw dots and undo. When implementing undo we refactored the code to make it easier to understand and maintain in future.

Taking leverage of the command design pattern and some domain modelling we could easily implement undo as an operation that removes that last command and executes all accumulated commands in order on a blank canvas.

In the next post we’ll improve how the stroke looks.

Cross Platform Mobile Test Automation and Continuous Delivery

$
0
0

Badoo develops multiple products with native applications on all major mobile platforms - Android, iOS, Mobile Web (HTML5) and Windows. Our most popular product, called Badoo, has 250M+ registered users worldwide.

With all our apps on a roughly two-week staggered release schedule, as QA, we would always be doing regression testing on at least one of the platforms at any given time. This requires a lot of manual hours and is a time-consuming job.

So three years ago we started a mobile test automation project at Badoo.

In the beginning, we focused on just the Android and iOS applications. Choosing a tool was a major challenge as there are many mobile automation tools available, each with its own advantages and limitations.

Our first tests were written with Robotium for Android and Kif for iOS. But after writing a few test cases, we realized that most of the business logic was shared across both platforms. Most platform-specific differences were in the actual application interactions.

This motivated us to experiment with cross-platform test automation tools. Later, once we had proven their success, we extended support to include Mobile Web.

Why Cross-Platform Test Automation?

  • Reusable code between different platforms
  • Clear separation of test logic from platform interaction code
  • One test case confirms application behaviour is the same across platforms
  • Faster development
  • Pooled resources

We are using the BDD tool Cucumber, using Given-When-Then format. The test scripts are written in natural language making them readable by anyone. We use the following open source test tools to support Android, iOS and Mobile web.

  • Calabash
  • Appium

Our next challenge was integrating the test automation project with our CI processes. We are now running a smoke test suite on every feature branch, as well as nightly system tests, to detect issues early in the development process. We have scaled up from running on a single Mac machine to using a distributed build system. We also had a cabinet built to display all the test automation devices connected to our build agents, running our test automation suite round the clock.

Test automation has helped us to achieve

  • Faster and more frequent releases
  • Early bug detection
  • More stable applications

If you’d like more details about Cross-Platform Mobile Test Automation (and Continuous Delivery as well!) check out my video presentation. It’s from when I was asked to be a speaker at Codefest, one of the biggest testing conferences in Russia, and share my experiences of testing at Badoo.

JaCoCo coverage in Cucumber on Android

$
0
0

In an ideal world, all requirements would be specified clearly, comprehensive tests would be derived from those requirements, and the whole of the application would be tested. In practice, we’re only human.

Coverage is a useful means of discovering which parts of your application have fallen between the cracks in your test cases, and an historical analysis on a per-test basis can alert you to shifts in implementation that might warrant attention. For management it’s a nice graph to admire. :)

It’s well supported on Android JUnit testing (previously with Emma, more recently with JaCoCo under Gradle), and Calabash-iOS already seems to support it, but a web search for Android coverage under Calabash-Android didn’t yield any results.

Rather than just presenting a rabbit pulled from a hat, here’s an outline of how I arrived at a solution.

The solution is at the end, if you can’t wait.

Calabash-Android lifecycle

From previous work, I already knew that Calabash tweaks a pre-built server, re-signs it (i.e. updates its META-INF directory) to account for the tweaks, and then runs it. What I didn’t realise was that while Calabash starts its server on the Android device using a normal adb shell am instrument JUnit command, it completely subverts the mechanism to give its server access to the instrumentation usually available only to JUnit tests.

Calabash’s ‘junit test’ runs under the test-runner CalabashInstrumentationTestRunner.java, which starts the web server ‘as early as possible’ and eventually runs its solitary test case in InstrumentationBackend.java, called ‘testHook’. This ‘test’ sets various things up, but ultimately performs a System.exit(0) if it sees a useable main activity, causing the entire JUnit test sequence to terminate at that point.

This use of System.exit(0) prevents InstrumentationBackend.tearDown() executing - which is there to shut down the server if the activity isn’t detected. It also forestalls the post-testing part of Android’s InstrumentationTestRunner (which is where coverage is usually dumped).

So far, so good.

Unfortunately, simply copying InstrumentationTestRunner’s coverage dumping code into a new endpoint in the Calabash InstrumentationBackend didn’t work immediately for me. It raised java.lang.IllegalStateException: JaCoCo agent not started, which the web suggested was caused by no instrumented class having been loaded by the current classloader.

Inspecting my APK’s classes.dex with baksmali showed no JaCoCo references, whereas an APK from a normal library JUnit coverage build showed classes with various synthetic ‘jacoco’ elements. Notably, none were library classes - I had enabled coverage in my APK’s libraries, but not in the app’s own (because at that stage the minimal application code was not so interesting).

It turns out that debug app builds link in release libraries, and only debug libraries are normally built with coverage. According to the Android team, you don’t need coverage for an app’s libraries because you should be unit-testing them as libraries, so coverage on Cucumber needs plumbing. After a fruitless diversion into the Android/Gradle plugin’s source code, I was delighted to find this workaround posted by Martin.n that I was able to apply and embellish.

After another fruitless diversion, trying to retrieve the application’s dependencies in order to locate their source files and class files (including looking through the androidDependencies target that spits the requisite library information onto the screen, but doesn’t make it readily available through an API), I decided to hard-wire the list of libraries. This later proved useful for unifying the normal JUnit library coverage into one report.

From there, it was just a matter of retrieving the coverage from the application under test just before calabash-android was about to kill the server, and before it tried to clear application data, both of which required only small additions to android/operations.rb. You’ll see this in the patch mentioned below.

Solution

The solution comprises: 1. a patch to calabash-android, 2. some additions to the gradle build, and 3. a few command-line changes on your build and calabash commands.

Additions to your build

At the bottom of this post, there’s a gradle file. We call it ‘jacococoverage.gradle’ and keep it in the root of the project.

Our build structure happens to have our apps all at the top project level, and our libraries are inside two top-level directories called ‘libraries’ and ‘components’.

root-+---FirstApp+---OtherApp+---libraries|+---SomeLibrary|+---etc.+---components+---etc.

If your structure differs, it shouldn’t be hard to adapt the sourcefiles and classfiles paths in the gradle build file’s (areas.components.list ?: {}).each { comp -> closure, and to add more such chunks, to match your structure.

To build your release libraries (and components) with coverage ‘on’, create a new file like the following, calling it ‘libcoverage.gradle’:

android{buildTypes{release{// Instrument all release libraries for calabash app coveragetestCoverageEnabled=project.has('globalCoverage')}}}

After you apply the Android plug-in in your libraries’ build.gradle files, apply this new file too as follows (adjust the path depending on where you put the file):

applyplugin:'com.android.library    apply from: '../../libcoverage.gradle'

Into your application’s build.gradle file, apply the attached jacococoverage.gradle file (in much the same way as you applied libcoverage.gradle to your libraries), and customise the section at its start.

defdefaultAreas(){[apps:[variant:'internalbadoo/QA',list:['FirstApp','OtherApp']],libraries:[variant:'release',list:['SomeLibrary','AnotherLibrary','etc']],components:[variant:'release',list:['SomeCustomViews']]]}

To control the classes which aren’t included for coverage, change this (subclasses are implicitly excluded):

defexclusionPatterns=[// Auto-generated classes'**/R','**/Manifest','**/BuildConfig',// Third-party classes'android/**/*','com/google/**/*','com/android/**/*','javax/**/*',// Boiler-plate classes'com/badoo/boiler/plate/**/*',]

Now build your app (and its libraries) from clean with -PglobalCoverage=true added to your gradle flags.

Patching Calabash-Android

Check out this patch to calabash-android, build it from its ruby-gem directory with rake build, then use gem install (or gem install --local) to install it. Now with the env-var COVERAGE_DIR pointing somewhere (we direct it into build/outputs/coverage-files inside the gradle project), run calabash however you normally run it. Something like:

export COVERAGE_DIR=~/git/badoo/build/outputs/coverage-files
 bundle exec calabash-android run your.apk -p android_tests ...

Generating the report

If your coverage .ec files are within the git repository (per the suggested COVERAGE_DIR), you can generate the report like this:

./gradlew yourApp:createJacocoReport

If you need to change the root of the tree where the coverage.*ec files are looked for, specify that with -PcoverageDataRoot=… - this is relative to the project root, but by default includes the whole project root.

If you need to change where the coverage reports are placed, specify -PcoverageOutDir=... but by default it’s build/outputs/reports/coverage.

You can tweak the ‘defaultAreas’ specified in the jacococoverage.gradle file with -PcoverageAreas, which takes a JSON-formatted string as an argument that can merge, remove, or overwrite components. There are examples in the jacococoverage.gradle file’s header comment.

Incidentally, because ALL coverage.ec files are incorporated, this will scoop up any normal JUnit coverage results into the final report. So unless you want that, be sure to clear them out beforehand or change the search root with -PcoverageDataRoot.

Addendum

I stumbled across a few things that may be of interest:

  1. Cucumber-Jvm - A Java implementation of Cucumber which apparently supports coverage. Sadly, we’re using the Ruby version and have common code between iOS and Web, so we can’t use this at first blush. You might find it useful.
  2. Jacoco unresolved dependency for the root project in multimodule build
  3. Android Gradle Plugin User Guide
  4. https://plus.google.com/+OleksandrKucherenko/posts/RreU44qmeuP concerning the generation of coverage.xml files
  5. https://code.google.com/p/android/issues/detail?id=76373#c11 the basis of our jacocococococoverage.gradle file.
  6. Yes, I know ‘JaCoCo’ stands for ‘Java Code Coverage’, making ‘jacococoverage’ somewhat tautological.

jacococoverage.gradle

importgroovy.json.JsonSlurper/* Universal coverage report generator. * * -PcoverageOutDir = location for report, default = root/build/outputs/reports/coverage * -PcoverageDataRoot = root from which to look for coverage.*ec files, within project * -PcoverageAreas = Json-style editing of the default areas to include in coverage as described below. *      * Override default variant for apps: *      -PcoverageAreas='{"apps":{"variant":"badoo/debug"}}' * * Exclude 'OtherApp' from coverage: *      -PcoverageAreas='{"apps":{"list-":["OtherApp"]}}' * * Include 'SecretApp' into coverage: *      -PcoverageAreas='{"apps":{"list+":["SecretApp"]}}' * * Exclude all apps from coverage: *      -PcoverageAreas='{"apps=":{}}' * * Override default variant for all areas: *      -PcoverageAreas='{"apps":{"variant":"badoo/debug"}, "libraries":{"variant":"debug"}, "components":{"variant":"debug"}}' * * Default edit operation is append for lists, deep merge for maps, and replace for values. */defdefaultAreas(){[apps:[variant:'internalbadoo/QA',list:['OurApp','OurOtherApp']],libraries:[variant:'release',list:['OurCommon','OurDownloader','This','That','TheOther']],components:[variant:'release',list:['SubsidiaryScreen']]]}defexclusionPatterns=[// Auto-generated classes'**/R','**/Manifest','**/BuildConfig',// Third-party classes'android/**/*','com/google/**/*','com/android/**/*','javax/**/*',// Boiler-plate classes'com/badoo/boiler/plate/**/*',]defexcludeClasses=exclusionPatterns.collect{"${it}.class,${it}\$*.class"}.join(',')configurations{jacocoReportAnt}dependencies{// Unfortunately, we are unable to have jacoco as a top-level target due to this project.android reference.jacocoReportAntgroup:"org.jacoco",name:"org.jacoco.ant",version:project.android.jacoco.version}// Try to find the named preferred variant root of class files, or choose something consistent and sensibledeffindVariant(dir,preferred){while(dir.listFiles().grep{f->f.isDirectory()}.size()>1&&!(['com','android','javax'].grep{newFile(dir,it).exists()})){defdirs=dir.listFiles().grep{f->f.isDirectory()}defpreference=dirs.grep{f->f.getName().equals(preferred)}dir=preference?preference.first():dirs.sort().last()}println"Var: ${dir}"returndir}// Edit 'into' based on 'from', return 'into' for conveniencedefdeepMerge(into,from){from.each{k,v->try{if(k=~/\+$/){into[k[0..-2]]+=v}elseif(k=~/-$/){into[k[0..-2]]-=v}elseif(k=~/=$/){into[k[0..-2]]=v}elseif(vinstanceofMap){deepMerge(into[k],v)}elseif(vinstanceofList){into[k]+=v}else{into[k]=v}}catch(Throwablee){thrownewRuntimeException("At key ${k} in either ${from.keySet()} or ${into.keySet()} from ${from} or ${into}",e)}}returninto}// wrapper for deepMerge to avoid Gradle swallowing the exception report.defmergeAreas(json){try{returndeepMerge(defaultAreas(),newJsonSlurper().parseText(json))}catch(Throwablee){e.printStackTrace(System.err)// Otherwise, error is swallowed.return[apps:[list:[]],libraries:[list:[]],components:[list:[]]]}}// Copy main stats to top of the table/page: dashboard friendly.defcopyTotalsToTop(root){root.eachFileRecurse{file->if(file=~/\.html$/){defxml=file.text// Changed /regex/ to 'regex' for formatting purposes here and below.(xml=~'<tfoot>(.*?)<\/tfoot>').each{match,row->xml=xml.replace('</thead>',"${row}</thead>")defcells=(row=~'<td(?: class=".*?")?>(.*?)<\/td>').collect{it[1]}xml=xml.replace('</h1>'," (line: ${cells[2]}  branch: ${cells[4]})</h1>")}file.withWriter{it<<xml}}}}defcoverLibrary(things,place){(things.list?:{}).each{sourcefiles{fileset(dir:file("${place}/${it}/src/main/java"))}classfiles{defdir=newFile("${place}/${it}/build/intermediates/classes")fileset(dir:findVariant(dir,things.variant),excludes:excludeClasses)}}}taskcreateJacocoReport{doLast{defroot=project.getRootDir()FilereportOutDir=file(project.has('coverageOutDir')?project.coverageOutDir:"${root}/build/outputs/reports/coverage")defcoverageDataRoot=project.has('coverageDataRoot')?newFile(root,project.coverageDataRoot):rootdefareas=mergeAreas(project.has('coverageAreas')?project.coverageAreas:'{}')printlnareasreportOutDir.deleteDir()reportOutDir.mkdirs()getAnt().taskdef(name:'reportWithJacoco',classname:'org.jacoco.ant.ReportTask',classpath:configurations.jacocoReportAnt.asPath)getAnt().reportWithJacoco{executiondata{coverageDataRoot.eachFileRecurse{if(it=~/coverage\..*ec$/){println"Cov: ${(''+it.length()).padLeft(6)} ${it}"fileset(file:it)}}}structure(name:'Unified coverage'){(areas.apps.list?:{}).each{app->sourcefiles{fileset(dir:file("${root}/${app}/src/main/java"))[file("${root}/${app}/build/generated/source/aidl/${areas.apps.variant}"),file("${root}/${app}/build/generated/source/rs/${areas.apps.variant}")].each{if(it.exists()){fileset(dir:it)}}}classfiles{[file("${root}/${app}/build/intermediates/classes/${areas.apps.variant}")].each{if(it.exists()){fileset(dir:it,excludes:excludeClasses)}}}}coverLibrary(areas.libraries,"${root}/libraries")coverLibrary(areas.components,"${root}/components")}html(destdir:reportOutDir)xml(destfile:newFile(reportOutDir,"report.xml"))}copyTotalsToTop(reportOutDir)println"Coverage: open ${reportOutDir}/index.html"}}

First Badoo Summer Internship Program

$
0
0

For the first time ever, Badoo has taken on an intern over the summer. We see it as an act of giving back, supporting young students in gaining relevant work experience and skills, while enjoying the pace of working for a big company. This internship program is our contribution to the community, and a way to say thanks to all those who let us be who we are - from our loyal users to the developers who make the open source components we use.

Anton and Joan

Having an intern around the place also has an interesting side effect. A fresh pair of eyes on problems that face the full-time team can result in novel perspectives and new ideas that will force team members to move out of their comfort zones. Also, when you teach complex topics to a really junior engineer, you need to make sure that you understand the fundamentals clearly yourself. This pushes you to dig deeper and provide good answers to questions you might be asked.

In the end it is a win-win situation, with good results for all the parties involved. To demonstrate this, we’d like to share the experiences of Anton Terekhov and Joan Molinas, mentor and intern in the first Badoo Summer Internship Programme, in their own words:

My experience as an internship mentor, by Anton Terekhov

Mentoring a junior developer helps you identify areas where you could be stronger. The intern is assigned with a programming task, and will eventually face some problems that demand deeper knowledge of the platform. He or she will ask you questions that you should be able not only to answer, but also explain in a simple manner. This requires more reading and investigation, so being a mentor helps you to become a better developer.

During the first days of the internship you must help the intern through the company’s onboarding process, demonstrating how to get all the resources and access required. Having worked in the company for some time, it’s easier to understand how to make this process quick and painless, and how to make a newcomer’s first days enjoyable and productive.

With a little time, the intern will start to understand shared best practices and learn how to produce code of the same high standard as the whole team. The job of a mentor is to explain the pros and cons of different approaches, and why the majority of the team decided on the one they implemented. This becomes especially evident during code reviews. From structure to naming conventions, everything is questioned, requiring a detailed walkthrough.

Being a mentor, you learn many new things. Technologies become outdated increasingly rapidly, and often approaches you’re used to are no longer the best ones. Interns are young and have only just started their careers, so they absorb new advances quickly, and adapt well to doing things in new and different ways. This will definitely broaden your view and add some techniques to your toolbox.

As a final note, it’s been a pleasure to see how a junior developer gains knowledge and how their code style becomes noticeably cleaner week after week. You see the intern growing as a professional, and you can be proud of your contribution to their training.

My experience as an intern, by Joan Molinas

“No matter how much you learn, there’s always more to know” - this sentence would summarise my internship experience at Badoo.

I started this adventure one month ago. Before I joined Badoo I was really excited about the idea of spending some time learning the tools and skills of the trade in a big company, but I couldn’t even imagine I was going to live all I lived during the last month.

I joined the company with a basic working knowledge of iOS programming and I saw how, day after day, I improved, thanks to all the hours the team has put into my training. I’ve learned many things, from basic programming principles to how to architect a complex application. I know that everything I’ve learned will be really useful to me from now on.

I’m also fascinated with the quality and dedication of the different teams that make up Badoo, and specially with the iOS team. Although I expected they would be good, I never imagined they would be some of the most talented engineers I’ve ever met.

This month I haven’t just learned programming fundamentals, as SOLID principles, graph searching algorithms or software design patterns, but many other skills related to the day to day of a big Internet company. For instance, I’ve participated in the recruiting process for many different candidates and I’ve seen how difficult such interviews are. I was lucky enough to cross the path of a really senior Product Manager, Pedro Mejuto, who taught me how to see software development from a really different perspective.

When my internship comes to an end, I’ll really miss Badoo. But now I have the motivation to keep on learning with the aim of returning some day as a fully-fledged iOS engineer. I’d like to thank Badoo for this awesome opportunity, and I’m very grateful to all and each employee for their kind, friendly and supportive behaviour. In particular my tutor Anton Terekhov, for all his patience, dedication and effort to make a good developer of me, and Rafael Lopez, who made possible everything I’ve just described, without doubt one of the best experiences in my life.

The Team

Let's build: Freehand Drawing in iOS - Part 3

$
0
0

This the third and final tutorial in which we build Freehand Drawing for iOS. In this part we’ll improve how the stroke looks and feels.

You can check the previous posts here:

In the last post we did a refactor and implemented undo functionality, thus completing all the features of our small drawing application. Now it is time to improve the look and feel of the drawing itself.

Lines

We implemented drawing by connecting the dot path the user creates with her finger. We connect the dots by drawing straight lines between touch points:

lines

This is very simple to implement but the drawing looks artificial and ‘computerish’. This is because a human would never draw this way, unless she stopped and changed direction at every point. This is less noticeable in our implementation because the stroke width is set to a big value. Let’s reduce it and adjust it from there. This is how the stroke looks:

lines2

Adding a curve

The first improvement we can make is to add some curvature when we connect the dots. To select how we join the user’s touches we need to satisfy two constraints:

  • The points are not known in advance as the user continually touches the screen and creates new points. We draw curves only knowing the previous points.
  • We can’t redraw segments when new points are added. This means we are restricted on how we ensure a good curve between segments.

Considering our restrictions, we could try to use the simplest and most popular widely used curve interpolation between two points: Bezier curves.

We won’t use [Catmull-Rom spline] due to a couple of reasons:

  • Catmull requires all points in advance.
  • We’d need to implement it ourselves or use a library. We can use Apple’s implementation of Bezier curves in Core Graphics framework.

In order to use cubic bezier curve, we need to specify two target points and two control points:

cubic_bezier

Similarly, a quadratic bezier curve is specified by two target points, but only one control point:

quadratic_bezier

When using Bezier paths, the choice of control points is vital, as they define the curvature and tangent direction on the target points. For now we’ll use quadratic Bezier because it only needs one control point, so it’s easier to calculate. We can adopt a cubic Bezier path later if the result is not satisfactory.

Curves with Bezier splines

We already know that choosing a good control point is key to ensuring our curves are smooth and continuous. If we were to take the control point as the midpoint between two touches, even if it seemed that it would work nicely, the curve would break at every point, because the tangents at the target points wouldn’t match:

controlpoint1

We can use a more sophisticated but still simple choice of control point. We’ll need three touch points. The control point will be the second touch. We will then use midpoints between first and second, second and third, as the target points. The line will not go exactly through the user’s touches, but the difference is small enough not to be noticed. Most importantly, the tangent is preserved at the target points, thus keeping a smooth curve throughout the user touches:

controlpoint2

We encourage you to check this excellent tutorial, which explains this choice of control points in depth. Now let’s modify our code to support drawing curves.

First we need to modify our LineDrawCommand. When the user only moves the finger producing two touches, we can’t draw more than a straight line, as we don’t have previous points data. Afterwards, to trace a quadratic bezier using the midpoints we will need three touches.

You can see all the changes here.

Let’s start by explicitly having a model of a segment in our code to increase readability:

structSegment{leta:CGPointletb:CGPointvarmidPoint:CGPoint{returnCGPoint(x:(a.x+b.x)/2,y:(a.y+b.y)/2)}}

We will initialise our draw command with one segment, and optionally a second one, to form the three required touch points. When all points are provided, the command will draw a curve. If only two points are provided, the command will draw a line instead.

structLineDrawCommand:DrawCommand{letcurrent:Segmentletprevious:Segment?letwidth:CGFloatletcolor:UIColor// MARK: DrawCommandfuncexecute(canvas:Canvas){self.configure(canvas)ifletprevious=self.previous{self.drawQuadraticCurve(canvas)}else{self.drawLine(canvas)}}privatefuncconfigure(canvas:Canvas){CGContextSetStrokeColorWithColor(canvas.context,self.color.CGColor)CGContextSetLineWidth(canvas.context,self.width)CGContextSetLineCap(canvas.context,kCGLineCapRound)}privatefuncdrawLine(canvas:Canvas){CGContextMoveToPoint(canvas.context,self.current.a.x,self.current.a.y)CGContextAddLineToPoint(canvas.context,self.current.b.x,self.current.b.y)CGContextStrokePath(canvas.context)}privatefuncdrawQuadraticCurve(canvas:Canvas){ifletpreviousMid=self.previous?.midPoint{letcurrentMid=self.current.midPointCGContextMoveToPoint(canvas.context,previousMid.x,previousMid.y)CGContextAddQuadCurveToPoint(canvas.context,current.a.x,current.a.y,currentMid.x,currentMid.y)CGContextStrokePath(canvas.context)}}}

The last change is to our DrawController. It needs to use the changed LineDrawCommand, and retain the state of previous segments as the user continues moving her finger.

// In DrawController.swiftprivatefunccontinueAtPoint(point:CGPoint){letsegment=Segment(a:self.lastPoint,b:point)letlineCommand=LineDrawCommand(current:segment,previous:lastSegment,width:self.width,color:self.color)self.canvas.executeCommands([lineCommand])self.lineStrokeCommand?.addCommand(lineCommand)self.lastPoint=pointself.lastSegment=segment}// Other minor cleanup necessary, please refer to the repository

If you run and test it now the stroke is much less blocky:

lines3

If you would like to improve the stroke even further, you can try adopting cubic bezier paths. To use those you’ll need an additional control point.

Changing the stroke width

Up until now, our stroke has been uniform in width. This is practical and easy to implement, but we can add a nice touch to give our feature a distinctive and playful look. We’ll change the width of the stroke depending on how fast the user is moving her finger.

We want to remind the user of something from the real world without emulating it perfectly, as we are not building a drawing application. We can even exaggerate the effect a bit to make it more interesting.

As we are not drawing triangles, but just using higher level line drawing commands, we only have control of the width of one segment. So we will need to add width to our segment structure.

The width of the current segment is inversely proportional to the speed of the movement. This means the faster the user moves the finger the thinner the stroke will be. The velocity is supplied by the gesture recogniser, so changing our DrawController to use variable width is very easy. We just need to use that velocity, and we can isolate the actual calculation of the width change in a free function.

You can also see these changes here.

funcmodulatedWidth(width:CGFloat,velocity:CGPoint)->CGFloat{letvelocityAdjustement:CGFloat=600.0// Trial and error constantletspeed=velocity.length()/velocityAdjustementletmodulated=width/speedreturnmodulated}extensionCGPoint{funclength()->CGFloat{returnsqrt((self.x*self.x)+(self.y*self.y))}}

This code will modulate the width but in a very strange way:

width1

There are two problems with our simple width modulation:

  1. Speed can change a great deal between touches.
  2. We don’t limit the width to between a minimum and a maximum.

To solve the first problem we’ll keep track of the previous speed and give more weight to it when calculating the modulated width. This will work for cases when the user makes sudden changes of speed, so the the actual change of width will be more gradual.

For the second problem we’ll just limit the output width to between a maximum and a minimum value.

These changes are here.

funcmodulatedWidth(width:CGFloat,velocity:CGPoint,previousVelocity:CGPoint,previousWidth:CGFloat)->CGFloat{letvelocityAdjustement:CGFloat=600.0letspeed=velocity.length()/velocityAdjustementletpreviousSpeed=previousVelocity.length()/velocityAdjustementletmodulated=width/(0.6*speed+0.4*previousSpeed)letlimited=clamp(modulated,0.75*previousWidth,1.25*previousWidth)letfinal=clamp(limited,0.2*width,width)returnfinal}extensionCGPoint{funclength()->CGFloat{returnsqrt((self.x*self.x)+(self.y*self.y))}}funcclamp<T:Comparable>(value:T,min:T,max:T)->T{if(value<min){returnmin}if(value>max){returnmax}returnvalue}// Additional cleanup and setup in draw controller to keep // track of previous width and previous velocity. See repository.

The parameters are a bit extreme to see the difference, but this is how the stroke looks with this code:

width2

Now we’re getting the impression of drawing with a dip pen. You’ll need to tweak the constants to your liking depending on how subtle you want it to be. We don’t want to simulate a real stroke with ink, but rather to give a playful and more realistic feel for the user.

Conclusion

We’ve improved the stroke of our small drawing application by connecting the dots using more than just straight lines. We’ve also achieved a more realistic and playful feel by changing the width of the stroke depending on the speed of user touches.

During the course of these tutorials we’ve seen the kind of technical challenges a developer may be faced with, and we’ve evolved our code by refactoring and redesigning as our requirements change.

The repository with all the code can be found here.


Click to git rebase

$
0
0

git rebase When we talk about automating the development and testing process, it might sound like it’s a very ambitious affair, and indeed it is. But if you break it into pieces, then individual fragments of the whole picture become visible. This process of fragmentation is highly important in two situations:



  • when actions that require concentration and accuracy are being performed manually;
  • when there are severe time constraints.

In our case, the time constraint is evident: releases are compiled, tested, and rolled out to the production server twice a day. Given the cramped time periods in a release’s life cycle, the process of deleting (rolling back) a buggy task from the release branch has great significance. And to realize that process, we use git rebase. Because git rebase is an entirely manually operation that requires attentiveness and exactness, and takes an extended period of time, we have automated the process of deleting a task from the release branch.

Git flow

Git is currently one of the most popular version control systems, and we use it effectively at Badoo. Working with Git is quick and simple.

flow scheme

A particular feature of our model is that we develop and test each job in a separate branch. The branch name consists of the JIRA ticket number and an arbitrary description of the job. For example:

BFG-9000_All_developers_should_be_given_a_years_holiday_(paid)

We compile the release and test from a separate (release) branch in which finished and tested jobs are merged in the development environment. Because we roll out code to our production server twice a day, it follows that we create two new release branches every day.

statuses

The release is formed by merging jobs into a release branch using the auto merge tool. We also have a master branch, which is a copy of the production server. After the release and each individual job have been integration tested, the code is sent to the production server and merged into the master branch.

When a release is being tested in the staging environment and a bug is found in one of the jobs but there is no time for a fix, we simply delete that job from the release using git rebase.

cats to release

Note: We do not use the git revert function in the release branch, because if a job is deleted from the release branch using git revert and the release branch is merged into the master branch, from which a developer then pulls fresh code into the branch where the bug originated, then he would have to do a revert on a revert in order to back out his changes.

In the next stage, we build a new version of the release, push it to the staging environment, test for bugs, run autotests, and then, if the tests pass, we push the code out to the production server. The main elements of this flow are entirely automated and operate in a seamlessly integrated process (until now only deleting a job from the release branch was performed manually).

cats flow

Statement of the problem

Let’s consider what can be used to automate the process:

  1. The release branch, which we are planning on pulling a ticket from, consists of two kinds of commits:
    • A merge commit that happens when merging working branches into the release branch and contains the ticket name in the commit message, since branches are named with a job prefix;
    • A merge commit that happens when automatically merging the master branch into the release branch. We apply patches in a semi-automated fashion to the master branch using our special DeployDashboard tool. Patches are applied to the appropriate ticket. Furthermore, the ticket number and patch description are given in the commit message.
  2. The built-in git rebase command, which is best used interactively because of the useful visualization.

Problems you may encounter:

  1. During the git rebase operation, a remerge of all commits in the branch is performed, beginning with the one being rolled back.
  2. If a merge conflict was resolved manually when the branch was created, Git does not save the resolution in memory. So when running git rebase, you will have to fix merge conflicts again manually.
  3. In this particular algorithm, conflicts are divided into two kinds:
    • simple - these conflicts occur because the version control system does not support remembering previously resolved merge conflicts;
    • complex - these conflicts occur because code was changed in a specific line (file), not only in the commit being deleted from the branch, but also in subsequent commits, which are remerged when git rebase is run. In this case, the developer fixes the conflict manually and performs a push into the release branch.

Git has an interesting feature, “git rerere”, which remembers the resolution of conflicts during a merge. It is enabled in automatic mode, but unfortunately it cannot help us here. This function only works when there are two long-standing branches that are constantly being merged. Git remembers these conflicts without any trouble.

We only have a single branch and if the -force function isn’t used when git pushing changes into the repository, then after each git rebase you have to create a new branch with a new trunk. For example, we apply the suffix _r1,r2,r3 … after each successful git rebase operation and perform a git push of the new release branch into the repository. Thus, the conflict resolution history is not preserved.

What do we ultimately want to achieve?

By clicking a certain button in our bug tracker:

  1. The job is deleted from the release automatically.
  2. A new release branch is created.
  3. The job’s status is changed to Reopen.
  4. All simple merge conflicts are resolved in the process of deleting the job from the release.

Unfortunately in any workflow it is impossible to resolve complex merge conflicts, so if a complex conflict occurs we notify the developer and release manager.

Main features

git merge

  1. Our script uses an interactive rebase and captures the commits in a release branch that has the job number to be rolled back.
  2. Upon finding the desired commits, the script deletes them and remembers the names of the files they affected.
  3. Then it remerges all commits, beginning with the commit we most recently deleted in the branch’s trunk.
  4. If a conflict occurs, then it checks the files involved in the conflict. If these files match the files of deleted commits, then we notify the developer and release manager that there has been a complex conflict that must be resolved manually.
  5. If the files do not match and there is a conflict, then it is a simple conflict. Then we take the code of the files from the commit, where the developer already resolved this conflict, from the origin repository.

Thus, we “run to the root of the branch”.

The probability that we will hit a complex conflict is almost non-existant, i.e. this process will execute automatically 99% of the time.

Implementation

Now let’s take a step-by-step look at what our script will do (in the example, only an automatic rebase is used and the script can simply be used in the console):

git commit

  1. We purge the repository and pull the latest version of the release branch.
  2. We get the top commit in the trunk with a merge in the release branch that we want to revert. a. If there is no commit, we report that there is nothing to revert.
  3. We generate an editor script that only deletes hashes of merge commits from the branch’s trunk, thus deleting them from the history.
  4. In the reverter script’s context, we specify the editor script (EDITOR) that we generated in the previous step.
  5. We run git rebase -ip for the release. We check the error code.
    • If it is 0, then everything succeeded. We jump to step 2 to find potential prior commits of the working branch being deleted.
    • If it is not 0, then there was a conflict. We try to resolve it:
      • We remember the hash of the commit that could not be applied. It is stored in the file .git/rebase-merge/stopped-sha
      • We examine the output of the rebase command to determine what went wrong.
        • If Git reports “CONFLICT (content): Merge conflict in “, then we compare the file with the version prior to that being deleted, and if it is the same (the was not changed in the commit), then we simple take the file from the build branch’s root and commit. If it differs, then we exit and the developer resolves the conflict manually.
        • If Git reports “fatal: Commit is a merge but no -m option was given”, then we simply repeat the rebase with the –continue flag. The merge commit is skipped, but the changes are not lost. This usually happens with the master branch, but it has already been pulled into the root of the branch and this merge commit isn’t needed.
        • If Git reports “error: could not apply… when you have resolved this problem run ‘git rebase –continue’”, then we run git status to get a list of files. If even one file from the status is in the commit that we are rolling back, then we skip the commit (rebase –skip) that we remembered in step 5.b.i, writing this fact to the log in order to notify the release manager and let him or her decide whether this commit is needed.
        • If none of the above happens, then we exit the script and report that something unexpected occurred.
  6. We repeat step 5 until we get exit code 0 or the cycle counter > 5, in order to avoid an endless loop.
<?php/*The code is from our deploy library and won't work when copy-pasted. The purpose of this code is to show the concept.*/functionrunBuildRevert($args){if(count($args)!=2){$this->commandUsage("<build-name> <ticket-key>");return$this->error("Unknown build!");;}$build_name=array_shift($args);$ticket_key=array_shift($args);$build=$this->Deploy->buildForNameOrBranch($build_name);if(!$build)returnfalse;if($this->directSystem("git reset --hard && git clean -fdx")){return$this->error("Can't clean directory!");}if($this->directSystem("git fetch")){return$this->error("Can't fetch from origin!");}if($this->directSystem("git checkout ".$build['branch_name'])){return$this->error("Can't checkout build branch!");}if($this->directSystem("git pull origin ".$build['branch_name'])){return$this->error("Can't pull build branch!");}$commit=$this->_getTopBranchToBuildMergeCommit($build['branch_name'],$ticket_key);$in_stream_count=0;while(!empty($commit)){$in_stream_count+=1;if($in_stream_count>=5)return$this->error("Seems rebase went to infinite loop!");$editor=$this->_generateEditor($build['branch_name'],$ticket_key);$output='';$code=0;$this->exec('git rebase -ip '.$commit.'^^',$output,$code,false);while($code){$output=implode("\n",$output);$conflicts_result=$this->_resolveRevertConflicts($output,$build['branch_name'],$commit);if(self::FLAG_REBASE_STOP!==$conflicts_result){$command='--continue';if(self::FLAG_REBASE_SKIP===$conflicts_result){$command='--skip';}$output='';$code=0;$this->exec('git rebase '.$command,$output,$code,false);}else{unlink($editor);return$this->error("Giving up, can't resolve conflicts! Do it manually.. Output was:\n".var_export($output,1));}}unlink($editor);$commit=$this->_getTopBranchToBuildMergeCommit($build['branch_name'],$ticket_key);}if(empty($in_stream_count))return$this->error("Can't find ticket merge in branchdiff with master!");returntrue;}protectedfunction_resolveRevertConflicts($output,$build_branch,$commit){$res=self::FLAG_REBASE_STOP;$stopped_sha=trim(file_get_contents('.git/rebase-merge/stopped-sha'));if(preg_match_all('/^CONFLICT\s\(content\)\:\sMerge\sconflict\sin\s(.*)$/m',$output,$m)){$conflicting_files=$m[1];foreach($conflicting_filesas$file){$output='';$this->exec('git diff '.$commit.'..'.$commit.'^ -- '.$file,$output);if(empty($output)){$this->exec('git show '.$build_branch.':'.$file.' > '.$file);$this->exec('git add '.$file);$res=self::FLAG_REBASE_CONTINUE;}else{return$this->error("Can't resolve conflict, because file was changed in reverting branch!");}}}elseif(preg_match('/fatal\:\sCommit\s'.$stopped_sha.'\sis\sa\smerge\sbut\sno\s\-m\soption\swas\sgiven/m',$output)){$res=self::FLAG_REBASE_CONTINUE;}elseif(preg_match('/error\:\scould\snot\sapply.*When\syou\shave\sresolved\sthis\sproblem\srun\s"git\srebase\s\-\-continue"/sm',$output)){$files_status='';$this->exec('git status -s|awk \'{print $2;}\'',$files_status);foreach($files_statusas$file){$diff_in_reverting='';$this->exec('git diff '.$commit.'..'.$commit.'^ -- '.$file,$diff_in_reverting);if(!empty($diff_in_reverting)){$this->warning("Skipping commit ".$stopped_sha." because it touches files we are reverting!");$res=self::FLAG_REBASE_SKIP;break;}}}return$res;}protectedfunction_getTopBranchToBuildMergeCommit($build_branch,$ticket){$commit='';$this->exec('git log '.$build_branch.' ^origin/master --merges --grep '.$ticket.' -1 --pretty=format:%H',$commit);returnarray_shift($commit);}protectedfunction_generateEditor($build_branch,$ticket,array$exclude_commits=array()){$filename=PHPWEB_PATH_TEMPORARY.uniqid($build_branch).'.php';$content=<<<'CODE'#!/local/php5/bin/php<?php$build='%s';$ticket='%s';$commits=%s;$file=$_SERVER['argv'][1];if(!empty($file)){$content=file_get_contents($file);$build=preg_replace('/_r\d+$/','',$build);$new=preg_replace('/^.*Merge.*branch.*'.$ticket.'.*into\s'.$build.'.*$/m','',$content);foreach($commitsas$exclude){$new=preg_replace('/^.*'.preg_quote($exclude,'/').'$/m','',$new);}file_put_contents($file,$new);}CODE;$content=sprintf($content,$build_branch,$ticket,var_export($exclude_commits,1));file_put_contents($filename,$content);$this->exec('chmod +x '.$filename);putenv("EDITOR=".$filename);return$filename;}

Conclusion

git push

Ultimately, we created a script that automatically deletes jobs from the release branch. We saved time in compiling and testing the release and almost entirely eliminated the human factor.

Of course, our script isn’t suitable for every Git user. In some instances it would be simpler to use git revert, but it’s best not to get carried away with that command, e.g. reverting a revert of a revert… We hope that the difficult git rebase command is now more understandable to you. And if you use git rebase regularly, we hope our script will work for your development and release process.

Ilya Ageev, Head of QA

clang-format as a guard for Objective-C code style

$
0
0

This article is about the experience of Badoo’s iOS team in making sure Objective-C code is compliant with the code style used.

Importance of the code style

The primary goal of all coding conventions is to unify code as much as possible and eliminate personality. It’s like handwriting, nobody cares about it until it has to be understood quickly by someone else, and it becomes a disaster. With that, it is important to realise that styling conventions are to be taken into account and followed with the only objective – they allow us to simplify readability and further maintenance of source code, especially when dealing with code written by others or a long time ago.

Every project made by a group of people faces the issues with code styles, and the iOS team at Badoo is no exception in this respect – Badoo Objective-C Style Guide defines our coding conventions.

Styling issues during code review

It’s important to follow defined conventions for all sources and every time, this is non-negotiable. But what guarantees they are followed? Code reading and code reviews, right?

It seems everyone can tell stories about times when his or her changes have been reworked because of a wrongly placed star, bracket or missed space. Great, but it’s just as easy to overlook such issues during the code reading as it is to make slips when writing. The worst part is that it requires additional attention to details from reviewers, and thus more important things (like the absence of an array boundary check) may not be noticed because of complaints about the position of brackets in the same area. Finally, it annoys developers when something has to be reworked because of a minor thing which does not affect the compiled code, as the next iteration will be delayed applying the changes, and reviewers distracted.

Automation of checks and clang-format

Fortunately, it is possible to make such checks automatically using scripts or tools.

For instance, Google uses a tool to assist with style guide compliance, but it is tightly tailored to Google coding conventions for C++. Other projects use similar tools, but they are often specific and cannot be configured.

Uncrustify is a much better option as it allows us to set up and configure lots of checks. However it doesn’t build a syntax tree, so it often mixes up syntax constructions and ends up with wrong corrections. It does not allow us to re-format code, so for instance when the name of function is changed it doesn’t take this into account for formatting of multi-line parameters. Finally, it doesn’t allow us to perform a check for particular lines of code, but for whole files only.

clang-format does not have so many options, but it also doesn’t have the issues mentioned. It builds a syntax tree and it uses fancy algorithms under the hood to resolve conflicts. It also exploits the capabilities of the same code parser as for creation of binaries, so it does not emit lots of false corrections.

We opted for the last variant and used clang-format:

  • All our attempts to extend Google’s linter with corresponding checks for Objective-C and adjust it to Badoo coding conventions did not lead to desirable results: lots of checks were still false and lots of statements were missed.
  • Configuration of Uncrustify took lots of time (it has up to 300 options!), but it still had issues with interpretation of simple Objective-C constructions: for instance, id<MyDelegate> delegate was misinterpreted with the compare operator.

Setting up clang-format

Set-up of clang-format is done using configuration files, but different parts of the project located in different folders may have different configurations. This was not the case for Badoo, so the only configuration file was generated in the root of source tree – it has to be named either _clang-format or .clang-format. clang-format has a set of predefined styles used in the following projects LLVM, Google, Chromium, Mozilla, WebKit, and GNU (not documented), so since Badoo’s Objective-C conventions are the closest to WebKit ones, it looked like this:

$ clang-format -style=WebKit -dump-config > .clang-format

Once it was done, the generated configuration file was adjusted to the conventions. This was pretty straightforward as the configuration file has a simple key-value structure.

Starting point: reformatting all up

In order to avoid further complaints and unnecessary debate it has to check and reformat existing code using the new tool. This allows us to white out the existing sources and continue with formatted ones. In Badoo it was done using a special Python script which enumerates sources and skips auto-generated and third party ones, as well as fixing copyright headers. This script is also used when a new version of clang-formatis introduced to automatically re-format corresponding sources.

Another question is when it should be done. Certainly initial reformatting produces a lot of changes, and this leads to conflicts with existing ones in branches. Due to this, it was decided to apply reformatting when there are few active tasks and not much work in progress, and at Badoo it was decided to run it on Jan 1st (it was also symbolic – the New Year is always associated with something new and surprising). In addition, it was a good marker that those changes are because of the style: from time to time it helps when checking changes with git blame. Also to avoid further complaints it was authored by a special bot, so all blame is focused on it, not a real person in the team.

Going forward: automatic reformatting and git hooks

Having the clang-format configured may be the end of the story.

If Xcode is the only tool used for dealing with sources, BBUncrustifyPlugin-Xcode plug-in can radically simplify development as it reformats sources using clang-format according to the configuration once the file is saved, or it may reformat only selected files or lines of code.

If esoteric source code editors like bbedit are used instead, then check out available extensions.

So far so good, but these approaches do NOT guarantee the committed source code actually corresponds to the conventions. With that in mind, we decided to add a corresponding check to avoid accidental commits which contradict the conventions. Since Badoo uses git as a revision control system, it was decided to add a pre-commit hook. This hook checks differences which are about to be committed against the conventions, and if it detects issues it complains and cancels the commit:

$ git diff
diff --git a/main.m b/main.m
index 294a954..967a7ae 100644
--- a/main.m
+++ b/main.m
@@ -21,8 +21,8 @@ int main(int argc, char *argv[]) {
 #else
             isLoggingEnabled = NO;
 #endif
-            NSDictionary *dict = @{ @"isLoggingEnabled" : @(isLoggingEnabled) };
-            [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
+            NSDictionary* configuration  = @{ @"isLoggingEnabled" : @(isLoggingEnabled) };
+            [[NSUserDefaults standardUserDefaults] registerDefaults: configuration];

         }

And if some issues are detected during the commit they are shown as follows:

$ git commit -a
ERROR: INCORRECT CODE STYLE
If you believe it is correct, use '-n' or '--no-verify' option to ignore this error.
Otherwise execute 'cat /var/folders/hs/p6xmm3hs3x51pzc2jd3__wrr0000gq/T/com.badoo.codestyle.4nvjrD.patch | patch -p0' to fix found issues.
Do NOT forget to add changes to the index if needed or use '-a' option, otherwise they will be lost.
--- main.m        (before formatting)
+++ main.m        (after formatting)
@@ -21,8 +21,8 @@
 #else
             shouldLog = NO;
 #endif
-            NSDictionary* configuration  = @{ @"isLoggingEnabled" : @(isLoggingEnabled) };
-            [[NSUserDefaults standardUserDefaults] registerDefaults: configuration];
+            NSDictionary *configuration = @{ @"isLoggingEnabled" : @(isLoggingEnabled) };
+            [[NSUserDefaults standardUserDefaults] registerDefaults:configuration];

         }

If the author of the commit agrees the found issues are reasonable, and not false complaints, the patch generated can be used to fix the committed changes as follows:

$ cat /var/folders/hs/p6xmm3hs3x51pzc2jd3__wrr0000gq/T/com.badoo.codestyle.4nvjrD.patch | patch -p0

Otherwise these complaints can be ignored by specifying -n/–no-verify options in the command line or just ignoring hooks in such tools like SourceTree. Unfortunately, git support in Xcode does not allow us to ignore installed hooks, so in order to commit changes which are falsely considered unacceptable it has to use an alternative approach.

The hook and the script to install it can be found here.

In order to re-use it in your project, it has to modify the path to the clang-format binary in your repository (change value for the binary variable in the pre_commit.py file) and optionally prefix for generated patches (by changing value of the patch_prefix variable in the same file):

....
binary = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', '..', 'bin', binary_name))
patch_prefix = 'com.badoo.codestyle.'
....

install_pre_commit_hook.py can be used easily to install the mentioned hook, another word to let git know about this hook.

Note, if you’re using BBUncrustifyPlugin-Xcode plug-in, it contains its own copy of the clang-format tool so it has to sync versions of clang-format used by the plug-in and git hook to avoid differences and surprises when committing changes.

What’s next: warnings for code style issues

The above-described git hook works out for us, but when it comes time to fix style issues it has to switch contexts – come back to the editor, make changes, and repeat commit. Wouldn’t it be great if such issues appeared as warnings when a source file gets compiled? In that case, all styling issues can be detected and fixed along the way and those which lead to unavoidable false complaints can be ignored.

It’s not currently possible to easily integrate such checks with the existing compiler infrastructure, so what can we do is to replace the default compiler with a wrapper by specifying the CC build setting:

CC = "$SRCROOT/tools/wrappers/clang.py”

The wrapper may look like as follows:

#!/usr/bin/env python


import os
import subprocess
import sys
import StringIO


binary_name = 'clang-format'
binary = os.path.abspath(os.path.join(os.path.dirname( __file__ ), binary_name))


def call_original_executable():
    command = ['/usr/bin/xcrun', '-r', 'clang']
    for argument in sys.argv[1:]:
        command.append(argument)
    subprocess.call(command)


def main():
    filename = None
    arguments = sys.argv
    arguments_length = len(arguments)
    for i in range(0, arguments_length):
        if arguments[i] == '-c' and i < arguments_length:
            filename = arguments[i + 1]
            break

    if not filename:
        call_original_executable();
        return

    command = [binary, '-style=file', filename]
    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, stdin=subprocess.PIPE)
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        sys.exit(p.returncode)


    formatted_code = StringIO.StringIO(stdout).readlines()
    with open(filename) as f:
        code = f.readlines()


    code_length = len(code)
    if code_length != len(formatted_code):
        sys.stderr.write(filename + ':0:0: warning: CAN NOT CHECK STYLE; DIFFERENT NUMBER OF LINES\n')
        exit(0)

    for i in range(0, code_length):
        if code[i] != formatted_code[i]:
            sys.stderr.write(filename + ':' + str(i) + ':1: warning: STYLE ERROR, EXPECTED:' + formatted_code[i] + '\n')

    call_original_executable()


if __name__ == '__main__':
    main()

This script extracts a path to file being compiled (if any), executes clang-format for it and obtains formatted lines of source code, then it compares formatted and unformatted lines. Once it finds a difference it emits warnings in the predefined format for errors, warning and notes. This format is not documented, but it’s quite simple and can be easily deduced by analysing output from the standard compiler:

FILENAME:LINE:COLUMN: warning|error|note: MESSAGE

For simplicity, if the number of formatted and unformatted lines differs, the script just gives up. Finally it executes the original (replaced) executable.

The problem with this approach is that for some reason Xcode just ignores these warnings. No matter how hard we tried – using both standard output and error streams, even if all ANSI scan codes are replicated – still Xcode does not digest the output.

This approach does not have so many benefits when it is used in the context of Xcode, but it may be useful with some other tools which are less restrict and appreciate these warnings. Perhaps someone reading this knows the secret sauce needed to make Xcode work, and if so, sharing the recipe in comments would be highly appreciated.

Batch updates for UITableView and UICollectionView

$
0
0

Apple did a tremendous job in giving developers such powerful building blocks as UITableView and UICollectionView. It’s even possible to claim that iOS wouldn’t have been such a success without these general purpose views. But unfortunately when it comes time to update these views in a batch fashion, it appears to be surprisingly hard. If you’ve tried to do it, you’re likely to be familiar with internal exceptions stating that the data model is not in sync with requested updates.

This article is about our attempts to update the mentioned views correctly according to obscure rules, in order to avoid these exceptions leading to crashes in runtime.

What is a batch update and when is it needed?

A batch update is a set of the following operations:

  • Insertion
  • Deletion
  • Move
  • Reload

It is applied for both items and sections in the view which are combined and executed together, perhaps in an animated fashion.

UICollectionView allows us to perform batch updates using the - [UICollectionView performBatchUpdates:completion:]method. For instance, to reload and insert items at particular index paths and remove the first section, it may look as follows:

[collectionViewperformBatchUpdates:^{[collectionViewreloadItemsAtIndexPaths:@[indexPathForUpdate]];[collectionViewinsertItemsAtIndexPaths:@[indexPath1ForInsertion,indexPath2ForInsertion2]];[collectionViewdeleteSections:[NSIndexSetindexSetWithIndex:0]];}completion:^(BOOLfinished){// Called async when all animations are finished; finished = NO if cancelled}];

In other words, all operations are to be specified in the given block, and it’s UIKit business to generate corresponding animations for us.

For UITableView it’s possible to do the same the but with additional calls of - [UITableView beginUpdates]-- [UITableView endUpdates]methods. It is even possible to specify which operations are to be animated and which are not:

[selfbeginUpdates];[tableViewreloadRowsAtIndexPaths:@[indexPathForUpdate]withRowAnimation:UITableViewRowAnimationFade];// With fade animation[tableViewinsertItemsAtIndexPaths:@[indexPath1ForInsertion,indexPath2ForInsertion2]withRowAnimation:UITableViewRowAnimationAutomatic];// With automatic animation[tableViewdeleteSections:[NSIndexSetindexSetWithIndex:0]]withRowAnimation:UITableViewRowAnimationNone];// Without animation[selfendUpdates];

UIKit does not allow us to track finishing of animations for UITableView out of the box like for UICollectionView, but fortunately it can be achieved easily using the CoreAnimationframework which drives the mentioned animations under the hood:

[CATransactionbegin];[CATransactionsetCompletionBlock:^{// Called async when all animations are finished};[tableViewbeginUpdates];// ...[tableViewendUpdates];[CATransactioncommit];

Encountered issues

As mentioned above, more often than not when you try to perform more or less complex batch updates, you’ll end up receiving an internal inconsistency exception like this:

*** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘attempt to insert item 2 into section 0, but there are only 2 items in section 0 after the update’

Under some circumstances it may even lead to memory issues with internal UIKit entities used for updates:

__pthread_kill+8pthread_kill+108abort+108szone_error+404free_list_checksum_botch+32tiny_free_list_remove_ptr+280zone_free_definite_size+1668-[UICollectionViewUpdatedealloc]+348-[UICollectionView_updateWithItems:tentativelyForReordering:]+2904-[UICollectionView_endItemAnimationsWithInvalidationContext:tentativelyForReordering:]+10116-[UICollectionView_performBatchUpdates:completion:invalidationContext:tentativelyForReordering:]+348-[UICollectionViewperformBatchUpdates:completion:]

If such issues appear you should consider yourself lucky, as even if you don’t get them users of your application might, and this will lead to crashes and plenty of disappointment.

Something weird is happening

Let’s read the documentation (again!)

Apparently something’s being done wrong, so let’s revisit documentation and read how batch updates are to be performed again:

Deletes are processed before inserts in batch operations. This means the indexes for the deletions are processed relative to the indexes of the collection view’s state before the batch operation, and the indexes for the insertions are processed relative to the indexes of the state after all the deletions in the batch operation.

This is helpful, but not very helpful, as even if the batch updates are performed in accordance with this statement it won’t work out as expected.

So let’s just work around these issues by using @try/@catch blocks to suppress propagation of internal exceptions and thus abnormal termination because of unhandled exceptions:

@try{[collectionViewperformBatchUpdates:^{// Do required updates}completion:nil];}@catch(NSException*exception){LOG_ERROR(@"Error updating collection view: %@",exception);}

This seems to be promising, but it fact it does not work out as the view appears in an incorrect internal state, meaning it’s not possible to interact with it in a predicted way after that. We are left with an approach like:

Just try everything possible and if it something does not work, make a workaround for it.

Found solutions and workarounds

An initial implementation of the update algorithm is implemented and various combinations of update operations are simulated on both a simulator and a real device. Once an issue is revealed the algorithm is adjusted and a test case scenario is implemented. In this scenario, we’ve noticed the following:

  • Simultaneous updates of sections and items lead to the mentioned exceptions and incorrect internal states of views, so once section updates are detected, views are reloaded completely and without animations using -reloadData methods. This is a significant limitation of this approach.

  • Reloads can not be used in conjunction with other changes, as under some circumstances they lead to memory corruption issues with internal UIKit entities. This has been worked around by asking a corresponding data source to update a specified cell in a way it’s reloaded (updated) when reused.

With that, if sections are NOT added, removed or moved often, consider reusing our solution.

Solution description

In order to generalise our solution, all collection items and sections are supposed to conform to the following protocols, respectively:

@protocolBMAUpdatableCollectionItem<NSObject>@property(nonatomic,readonly,copy)NSString*uid;@end@protocolBMAUpdatableCollectionSection<BMAUpdatableCollectionItem>@property(nonatomic,copy)NSArray/*<id<BMAUpdatableCollectionItem>>*/*items;@end

Once both old and new data models are available, it’s possible to calculate necessary updates and apply them using extension methods of the UITableView and UICollectionView classes.

Batch Updates Diagram

NSArray/*<id<BMAUpdatableCollectionSection>>*/*oldSections=...;NSArray/*<id<BMAUpdatableCollectionSection>>*/*newSections=...;[BMACollectionUpdatecalculateUpdatesForOldModel:oldSectionsnewModel:newSectionssectionsPriorityOrder:nileliminatesDuplicates:NOcompletion:^(NSArray*sections,NSArray*updates){[selfperformBatchUpdates:updatesforSections:sections];}];
@implementationTableViewController-(void)performBatchUpdates:(NSArray*)updatesforSections:(NSArray*)sections{[self.tableViewbma_performBatchUpdates:updatesapplyChangesToModelBlock:^{self.sections=sections;}reloadCellBlock:^(UITableViewCell*cell,NSIndexPath*indexPath){[selfreloadCell:cellatIndexPath:indexPath];}completionBlock:nil];}@end
@implementationCollectionViewController-(void)performBatchUpdates:(NSArray*)updatesforSections:(NSArray*)sections{[self.collectionViewbma_performBatchUpdates:updatesapplyChangesToModelBlock:^{self.sections=sections;}reloadCellBlock:^(UICollectionViewCell*cell,NSIndexPath*indexPath){[selfreloadCell:cellatIndexPath:indexPath];}completionBlock:nil];}@end

Please note that when a full reload of view is needed, the array of updates specified in the block is nil, so once they are propagated to extensions methods the reload is done automatically. Implementation of - [Controller reloadCell:atIndexPath:] is to be the same as for the corresponding data source method: - tableView:cellForRowAtIndexPath: or - collectionView:cellForItemAtIndexPath::

-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionViewcellForItemAtIndexPath:(NSIndexPath*)indexPath{UICollectionViewCell*cell=[self.collectionViewdequeueReusableCellWithReuseIdentifier:@"cell"forIndexPath:indexPath];[selfreloadCell:cellatIndexPath:indexPath];returncell;}

Please use our approach if it’s applicable to your needs, and do share your ideas or criticisms by adding comments to this article.

The technology of billing - how we do it at Badoo

$
0
0

There are many ways to monetize your project, but all of them have one thing in common – the transfer of money from the user to a company account. In this article we will discuss how this process works at Badoo.

What do we mean by ‘billing’?

Billing for us concerns all things related to the transfer of money. For example: pricing, payment pages and payment processing, the rendering of services and promo campaigns as well as the monitoring all of these things.

In the beginning, as with most startups, we had no paid services at all. The first steps towards monetization took place in 2008 (well after the official site launch in 2006.) We selected France as our guinea-pig and the only available payment method at that time worked via SMS. For payment processing we used a file system. Each incoming request was put into a file and moved between directories by bash-scripts, meaning its status changed during processing. A database was used only for registering successful transactions. This worked pretty well for us, but after a year this system became difficult to maintain and we decided to switch to using just a database.

This new system had to be re-worked quickly, as up till then we had been accepting payments in only a limited number of countries. But this system had one weak point – it was designed solely for SMS payments. To this day we still have some odd leftovers of this system in our database structure, such as fields MSISDN (mobile phone number) and short code (short number for premium SMS) in a table of successfully processed payments.

Now we receive payments from countries all over the world. At any given second at least a few users are trying to buy something on Badoo or through our mobile applications. Their locations are represented in this “Earth at Night” visual:

Earth

We accept payments using more than 50 payment methods. The most popular are credit card, SMS and direct billing, and purchases via the Apple Store and Google Play.

Pay

Among them you can find such leftfield payment options as IP-billing (direct payments from your internet provider account), landline payments (you have to call from your landline and confirm payment). Once we even received a payment via regular mail!

Letter

Credit card and bank payments

All payment systems have an API and work by accepting payments from their users. Such direct integrations work well if you have only a few of them and everything runs smoothly. But if you work with local payment systems it starts to become a problem. It is becoming harder and harder to support a lot of different APIs for several reasons: local laws and regulations are different, a popular local payment system provider may refuse to work with foreign clients, even signing a contract can draw out the process substantially. Despite the complexity of local payment methods though, adopting many of them has proven to be quite a profitable decision. An example of this is the Netherlands, which had not previously been a strong market for us. After we enabled a local payment system named iDeal, however, we started to take in 30-40% more profit.

Where there is demand, usually there’s someone ready to meet it. Many companies known as ‘payment gateways’ work as aggregators and unify popular payment systems – including country-specific ones – under one single API. Via such companies, it suffices to perform an integration only once and after that one gets access to many different payment system around the world. Some of them even provide a fully customizable payment page where you can upload your own CSS & JS files, change images, texts and translations. You can make this page look like part of your site and even register it in a subdomain such as “payments.example.com”. Even tech-savvy users might not understand that they just made a payment on a third-party site.

Which is better to use? Direct integration or payment gateways? First of all it depends on the specific requirements of the business. In our company we use both types, because we want to work with many different payment gateways and sometimes make direct integrations with payment systems. Another important factor in making this decision is the quality of service provided by a payment system. Often payment gateways offer more convenient APIs, plus more stable and higher-quality service than source payment system.

SMS payments

SMS payments are very different to other systems. In many countries they are under very strict control, especially in Europe. Local regulators or governments can make demands regarding all aspects of SMS payments. For example specifying the exact text sent via SMS or the appearance of the payment page. You have to monitor changes and apply them in time. Sometimes requirements can seem very strange, for example in Belgium you must show short code white on black with price nearby. You can see how this looks on our site below.

SMS

Also there are different types of SMS-billing: MO (Mobile Originated) and MT (Mobile Terminated). MO-billing is very easy to understand and implement. As soon as a user sends an SMS to our short number we receive money. MT is a bit more complicated. The main difference is that a user’s funds are not deducted from the moment he or she sends the SMS, but when a message from us is recieved with a notification that he or she is being charged. Through this method, we get the money only after receiving delivery notification of this payment message.

The main goal of MT-billing is to add an additional check on our side before the user sends money, preventing errors that occur due to user-misspelled SMS texts. Using this method, the payment process consist of two phases. First, the user initiates payment and second, they receive confirmation. In some countries the payment process for MT-billing follows one of these variants:

  • the user sends an SMS on short number, we receive it and check that the text is correct, etc. We send a free message with custom text, which the user has to answer, confirming the payment. After that we send a message that they have been charged
  • same as above, but instead of responding directly to the free message the user has to enter a PIN code from it on the Badoo site
  • the user enters their phone number on Badoo, we send a free message with a PIN. The user then enters the PIN code on Badoo, and after checking this, we send the payment message

For SMS payments we use only aggregators. Direct integrations with operators are not profitable, because you have to support a lot of contracts in many countries, which increasingly requires the involvement of accountants and lawyers.

Technical details

Badoo works on PHP and MySql. For payment processing we also use the same technologies. However billing application works on separate pools of servers. These are divided into groups, such as servers to process income requests (payment pages, notification from aggregators, etc), servers for background scripts, database servers and special groups with increased security where we process credit cards payments. For card payments, servers must be compliant with PCI DSS. Its security standards were developed in coordination with Visa, Master Card, American Express, JCB and Discover for companies who process or store the personal information of their cardholders. The list of requirements which have to be met to use these systems is quite long.

As database servers we use two MySql Percona servers, working in master-master replication. All requests process via only one of them - the second is used for hot-backup and other infrastructure duties, such as heavy analytical queries, monitoring queries and so forth.

The whole billing system can be divided into few big parts:

  • Core - the base entities needed for payment processing such as Order, Payment and Subscription
  • Provider plugins - all provider-related functionality such as implementation of API and internal interfaces
  • Payment page - where you can choose a product and payment method

In order to integrate a new payment provider, we need to create a new plugin which is responsible for all communication between us and the payment gateway. These can be of two types, depending whether we initiate the request (pull requests) or the payment provider initiates it (push requests). The most popular protocol for pull-requests is HTTP, either in itself or as transport for JSON/XML. The REST API (which has gained a certain degree of popularity recently) we haven’t encountered very often. Only new companies or companies who reworked their API recently offer it. For example with the new PayPal API or the new payment system used by the UK’s GoCardless company. The second most popular transport for pull requests is SOAP. For push requests mostly HTTP is used (either pure or as transport), and SOAP only rarely. The only company that comes readily to mind that offers SOAP push notifications is the Russian payment system QIWI.

After the programming part is finished the testing process begins. We test everything several times in different environments: the test environment, in shot (internal domain with only one particular task and working production environment), in build (pre-production version of code which is ready to go to live) and in the live environment. For more details about release management at Badoo please visit our blog: (http://techblog.badoo.com/blog/2013/10/16/aida-badoos-journey-into-continuous-integration/).

For billing tasks there are some peculiarities. We have to test not only our own code but how it interacts with third party systems. It’s nice if the payment provider offers their own sandbox which works the same as our production system, but if not we create stubs for them. These stubs emulate a real aggregator system and allow us to do manual and automatic testing. This is an example of a stub for one of our SMS providers.

Letter

After passing through the test environment we check how it will work with the real system, i.e. making real payments. For SMS payments, we often need to get approval from local regulators, which can take a few months. We don’t want to deploy semi-ready code on production so as a solution we create a new type of environment external shot. This is our regular shot, a feature branch with one task, but accessible by external sub-domain. For security reasons we create them only if needed. We send links to external shots to our partners and they can test changes at any time. It’s especially convenient when you work with partners from another hemisphere where the time difference can be up to 12 hours!

Support and operation

After a new integration goes live we enter the stage of its support and operation. Technical support occupies about 60-70% of our work time.

Support

By support I mean primarily customer support. All easy cases are solved by the first line of support. Our employees know many different languages and can translate and attend to customer complaints quickly. So only very complicated cases end up on the desks of our team of developers.

The second component of support is bug fixing or making changes to current integrations. Bugs appear due to multiple reasons. Of course the majority are a result of human error, i.e. when something is implemented in the wrong way. But sometimes it can result from unclear documentation. For example, once we had to use a Skype chat with a developer of a new payment system instead of documentation. At other times a payment provider makes changes on their side and forgets to notify us. One more point of failure is third party systems, as a payment provider’s aggregate payment services error can occur not on their side, but on their partner’s side.

In order to solve such cases quickly we maintain detailed logs. These contain all communications between us and payment providers, all important events, errors during query processing and so on. Each query has its own unique identifier through which we can find all rows in logs and reconstruct the steps of an execution query. It’s especially helpful when we have to investigate cases that happened a few weeks or months ago.

So that’s how billing is organized at Badoo! There are still many interesting topics we plan to explore in future, such as monitoring, PCI DSS certification, and re-working bank-card payments. If you have questions or suggestions for future articles, please leave a comment for us below.

Parallel Calabash Testing on iOS

$
0
0

We want our users to have the same experience of Badoo on mobile regardless of which platform they run, so we have a battery of 450 Cucumber tests and growing that we run against all platforms: Android, iOS, and mobile web (we’re working to add Windows). These 450 tests take between 8 and 12 hours to run, which makes for a very slow feedback loop, which makes for grumpy developers and grumpy managers.

Although Calabash only supports a single thread of testing at a time, it’s easy enough to run with multiple Android devices using the Parallel Calabash project from GitHub: just plug in a few phones and/or simulators, and the tests complete in a fraction of the time. The more devices, the faster things go, turning those frowns upside-down.

Unfortunately, it’s long been understood that running multiple iOS tests on a single MacOS host is impossible: you can only run one simulator at a time, the ‘instruments’ utility can only control one simulator at a time, and because of this anyone automating iOS - like Calabash - has made reasonable assumptions that reinforce the situation.

A little knowledge is sometimes a useful thing

It was obvious that we could run these tests in parallel on one host by setting up virtual machines, but that’s quite a heavyweight solution. After some simple experiments (which I conducted because I didn’t know any better) it became clear it would be possible to run the tests on the same host as two users, each with a desktop: one normal log-in, the other via TightVNC (because Apple’s Screen Sharing doesn’t allow you to log into your own machine).

A little checking around on the web revealed that with XCode 6.3, instruments quietly began to be able to run multiple devices at once from the command-line. We haven’t noticed any announcement from Apple about it, and we’ve found that it’s only XCode 7 that makes it reliable for us, but it’s a welcome change all the same.

With this insight, I set about adding iOS support to the Android-based Parallel_Calabash project on GitHub - not the most elegant solution, but eminently practical.

The long and winding road

Initially it was an optimistic tinkering: copying the application directory for each ssh user and tweaking the plists to set the Calabash endpoint to different ports. Then followed a substantial amount of trailing around inside Calabash-iOS to understand its internals to find out why only one simulator was resetting, why they were pretending to be real phones, and various other issues.

I worked out a trivial Calabash monkey-patch to resolve the resetting issue and puzzled though the assumptions causing the rest.

Then there was a similar amount of hair-pulling to arrange that the test user accounts each had an active desktop: I initially started looking into TightVNC to disable its pop-up dialogue, then send automated keypresses to choose a user and type its password, but switched to Apple’s Screen Sharing once I found that it could already be supplied with a username and password directly (vnc://user:pass@host:port), and foxed into logging back into the local machine with an ssh tunnel (ssh -L 6900:localhost:5900 user@localhost).

I had an unexpectedly frustrating and drawn-out struggle to get the logging-in automated: despite having been told to log in as a particular user, Apple’s Screen Sharing insists on asking

Apple Screen Sharing... ORLY!? YARLY!!

There seems to be no way to forestall that question (such as, adding ?login=1 to the VNC URL), so I ended up trying to run AppleScript from a shell script to respond, and then fighting with the UI Automation security system to authorise it - and losing. I ultimately rewrote the shell script completely in AppleScript - an experience reminiscent of coding 1970s COBOL - and then fighting with the UI Automation security system to authorise that instead - the result is misc/autostart_test_users, mentioned below.

Then I had to find out how to authorise that automatically on each testing host, and finally how to create all the test users automatically, without needing to confirm manually on the first login that - yes, these robots aren’t interested in iTunes accounts. You’ll find this last bit easily enough if you search the web for DidSeeSyncSetup, but you have to know to look for that! This is all coded into misc/setup_ios_host.

The final hurdle was that some of our tests do a little bit of screen-scraping with Sikuli to log in to Facebook. I discovered that we needed to use Java 1.7 (not 1.8) and set ADK_TOOLKIT=CToolkit to let a Java program invoked from an ssh context access the desktop.

So now we have a working system, and the maintainer of Parallel Calabash has incorporated the iOS solution into the main branch on GitHub.

Setting up

Each test machine has a main user, in our case the TeamCity agent user, and a set of test users. I’ve automated most of the set-up.

  1. On a Mac/Unix machine, run: git clone https://github.com/rajdeepv/parallel_calabash

  2. Now edit parallel_calabash/misc/example_ios_config if you need to - perhaps add a few more to the list of USERS (we’re using 5 for sysctl hw.logicalcpu = 8). It’s safe to run this script against a machine that’s already been set-up, if you want to change the settings.

  3. Make sure you can ssh to your main test user on the test machine (the test user must be an administrator account), then run cd parallel_calabash/misc; ./setup_ios_host mainuser targethost example_ios_config It should report success for each new QA user.

  4. Add misc/autostart_test_users.sh to your build script (or copy/paste it in).

  5. Then change your build’s invocation of calabash to use parallel_calabash as follows:

Parallel_calabash performs a dry run to get a list of tests that need to be run, and then runs the tests for real, so you will need to separate your calabash options into report-generation options (which aren’t used during the dry run) and the other options. For instance:

bundle exec parallel_calabash
  --app build/Applications/Badoo.app
  --ios_config /Users/qa/.parallel_calabash.iphonesimulator
  --simulator 'com.apple.CoreSimulator.SimDeviceType.iPhone-6 com.apple.CoreSimulator.SimRuntime.iOS-8-3'
  --cucumber_opts '-p ios_badoo IOS_SDK=iphonesimulator'
  --cucumber_reports '--format pretty -o build/reports -p parallel_cucumber_reports'
  --group-by-scenarios
  --device_target 'iPhone 6 (8.3)'
  --device_endpoint 'http://localhost:37265'
  features/

Briefly:

  • --app puts parallel_calabash in iOS mode, and tells it where to find the app build.
  • --ios_config says which config to use - we select between simulator or device configs.
  • --simulator says which simulator to clone for parallel users
  • --cucumber_opts gives the options for use with dry_run to get a list of tests
  • --cucumber_reports gives the options for use with the actual test-runs
  • --group-by-scenarios says to share tests equally between the parallel users
  • --device_target and –device_endpoint gives the default simulator to use if the configuration doesn’t specify test users (in which case, the main user will run all the tests by itself, on its own desktop, as if you were using calabash-ios directly)
  • features/ is the standard general test specification.

Next

There are a few developments I’m planning to implement:

  • Allowing parallel_calabash to control users on additional machines, for even more parallelism.
  • Feeding past test timings to parallel_calabash so it can try to arrange for all users to finish at the same time - less idle time means faster test results (Ideally, all this parallelism would happen within Cucumber, and Cucumber’s threads would request a test from a central list as it completes each current test, but that’s complex).
  • Using TightVNC instead of Apple’s Screen Sharing to enable video capture of each test: if a secondary user tells QuickTime to record, it actually records a garbled version of the primary desktop. I would arrange for TightVNC to encode its own video stream. Currently, we enable video capture only during a re-run of fewer than 40 failures which has been forced into non-parallel mode by using --device_filter=something-matching-no-device.
  • While I’m messing about with VNC, I might also investigate a Sikuli-like screen scraping scripting mechanism - since I’ll need to arrange something like that to log in anyway.
Viewing all 132 articles
Browse latest View live