<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>atodorov.org</title><link href="http://atodorov.org/" rel="alternate"></link><link href="http://atodorov.org/atom.xml" rel="self"></link><id>http://atodorov.org/</id><updated>2020-02-11T13:00:00+02:00</updated><entry><title>Open-Source Security Best Practices You Can't Ignore in 2020</title><link href="http://atodorov.org/blog/2020/02/11/open-source-security-best-practices-you-cant-ignore-in-2020/" rel="alternate"></link><updated>2020-02-11T13:00:00+02:00</updated><author><name>Gilad David Maayan</name></author><id>tag:atodorov.org,2020-02-11:blog/2020/02/11/open-source-security-best-practices-you-cant-ignore-in-2020/</id><summary type="html">&lt;p&gt;&lt;img alt="cyber-security image" src="/images/cyber-security.jpg" title="cyber-security image" /&gt;&lt;/p&gt;
&lt;p&gt;Open source components are incredibly useful in shortening development time.
Open source projects are created, maintained, and used by developers of all
levels and companies of all sizes. However, you can’t always determine who
created the code and who edited the project. For all you know, there’s a
piece of spyware hiding somewhere in the codebase. Read on to learn how to
apply open source security in 2020.&lt;/p&gt;
&lt;h2&gt;What Is Open-Source Software?&lt;/h2&gt;
&lt;p&gt;Open-source software uses freely available code so that anyone can view
and modify it. It is created collaboratively by communities of developers
at no charge. Some of the most popular open-source programs are Linux,
Kubernetes, Jenkins, and WordPress.&lt;/p&gt;
&lt;p&gt;Open-source software can have many
&lt;a href="https://levelup.gitconnected.com/understanding-open-source-license-types-5a577c4a09d5"&gt;different licensing terms&lt;/a&gt;.
There are more than 1400 different open-source licenses, the most common of which are
MIT, GPL, and Apache. Most licenses have two things in common:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Licenses do not require a license fee for the software&lt;/li&gt;
&lt;li&gt;Licenses allow anyone to contribute or modify to the program&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Open-source software isn’t always free of charge-companies often charge for support,
implementation, and additional features added on to open-source components. However,
open-source software can be cheaper to implement. This cost savings is why modern
enterprise software relies heavily on open source components. Likewise, many popular
commercial applications use thousands of open source components as part of their code.&lt;/p&gt;
&lt;h2&gt;Open-Source Risks You Must Know About&lt;/h2&gt;
&lt;p&gt;There are several risks you might face when using and including open-source components.&lt;/p&gt;
&lt;h1&gt;Public Nature of Vulnerabilities&lt;/h1&gt;
&lt;p&gt;Open-source code is publicly available for inspection. This allows community members
to contribute to identifying and fixing vulnerabilities. Ideally, contributors can
develop patches quickly, before the vulnerability is made public.&lt;/p&gt;
&lt;p&gt;Once discovered, open-source vulnerabilities are published on the National Vulnerability Database
(&lt;a href="https://nvd.nist.gov/"&gt;NVD&lt;/a&gt;). This database is publicly available and searchable,
meaning that both open-source users and hackers can see vulnerability information.
Hackers use this public availability to their advantage, attempting to exploit
vulnerabilities as soon as a flaw is announced. This can enable hackers to attack
systems before users get a chance to apply patches.&lt;/p&gt;
&lt;p&gt;A well-known example of this exploitation is the Equifax breach, in which 143 million
records were compromised. This breach occurred because attackers were able to exploit
a known vulnerability in the open-source Apache Struts framework. Although this
vulnerability was made public several years before, Equifax never patched their systems
to protect against it.&lt;/p&gt;
&lt;h1&gt;License and Use Infringement&lt;/h1&gt;
&lt;p&gt;Open-source projects lack standard commercial controls, trusting contributors to act ethically.
Unfortunately, this means that proprietary code may get included in projects without a
project maintainer’s awareness.&lt;/p&gt;
&lt;p&gt;An example of this occurring was seen in a case brought by SCO Group.
They accused IBM of including part of their proprietary code, into Project Monterey.
This code was unknowingly incorporated through open-source components that IBM included in the project.&lt;/p&gt;
&lt;h1&gt;Operational Risks&lt;/h1&gt;
&lt;p&gt;Operational inefficiencies can be a major source of risk when using open-source
components. In particular, inefficiencies caused by inadequate tracking or
monitoring of components. If you are unaware of what components you have or where
components are stored, you cannot ensure your systems are up to date.&lt;/p&gt;
&lt;p&gt;The possibility of losing support for a component is another risk you might face.
Open-source projects are based on voluntary engagement. If a community loses
interest in a project, it can see decreased support or be dropped entirely.
For such projects, you become directly responsible for ensuring that vulnerabilities
are identified and patched.&lt;/p&gt;
&lt;p&gt;To address these risks, you need to ensure that you maintain an inventory of components.
Doing so can provide visibility of your risks and can help ensure that you are using
components uniformly. Often, this means using software composition analysis tools to
automate this process and reduce manual labor.&lt;/p&gt;
&lt;h2&gt;Best Practices For Using Open-Source Securely in 2020&lt;/h2&gt;
&lt;p&gt;As the number of open-source projects increases, the likelihood that your systems will
include open-source components increases. To ensure that these components provide
maximum benefit with minimum risk, there are several
&lt;a href="https://www.whitesourcesoftware.com/open-source-security/"&gt;open source security&lt;/a&gt;
best practices you should adopt.&lt;/p&gt;
&lt;h1&gt;Balance Functionality and Risk&lt;/h1&gt;
&lt;p&gt;You may be able to gain the functionality you need with just part of an
open-source project. When considering the inclusion of an open-source project,
evaluate its components before you include anything. You may find that you only
need one library or service instead of an entire project. By limiting what you include,
you can reduce the risk of including additional vulnerabilities and simplify integration.&lt;/p&gt;
&lt;h1&gt;Consider Historical Security&lt;/h1&gt;
&lt;p&gt;To be considered secure, code must be reviewed and tested for vulnerabilities.
However, testing takes time and testing tools can be expensive so it may be
overlooked in open-source projects. You can get a better idea of the overall
security of a project by evaluating how security is addressed in a project’s
documentation. If a project doesn’t specify how vulnerabilities are identified
or what measures are taken to prevent flaws, you should be wary.&lt;/p&gt;
&lt;p&gt;Before including components, consider the security history of a project,
including the average number and type of bugs per release. If a project has a
history with lots of vulnerabilities, consider looking for an alternative.
You should also take into account how long it takes a community to fix vulnerabilities
once reported. Slow fixes can signal weak community support or significant issues
with the source code.&lt;/p&gt;
&lt;h1&gt;Consider Community Size and Engagement&lt;/h1&gt;
&lt;p&gt;Open-source software is typically supported by volunteers, including amateur developers.
This means projects can suffer from a lack of consistency. Ideally, projects have a
medium to large community base. This signals that quality is likely to be higher
and that projects are unlikely to be abandoned.&lt;/p&gt;
&lt;p&gt;You should also consider the size and frequency of releases a community is putting out.
If releases are haphazard or infrequent, you will have a harder time maintaining any
components you include. For the most reliable projects, release schedules are set
and you can anticipate the amount of effort to devote to maintenance.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hopefully, this article helped you learn the importance of open source security.
In a time when networks become increasingly distributed, securing your applications
becomes a crucial element of the development process. Many developers have already
realized that and are in the process of shifting security to the left. That means
you’re putting security as a top priority throughout all development stages to
ensure your code is as secure as possible.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Author Bio&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gilad David Maayan" src="/images/giladimage.jpg" title="Gilad David Maayan" /&gt;&lt;/p&gt;
&lt;p&gt;Gilad David Maayan is a technology writer who has worked with over 150 technology
companies including SAP, Samsung NEXT, NetApp and Imperva, producing technical and
thought leadership content that elucidates technical solutions for developers and IT leadership.&lt;/p&gt;
&lt;p&gt;LinkedIn:
&lt;a href="[https://www.linkedin.com/in/giladdavidmaayan/"&gt;https://www.linkedin.com/in/giladdavidmaayan/&lt;/a&gt;&lt;/p&gt;</summary></entry><entry><title>Comparing equivalent Python statements</title><link href="http://atodorov.org/blog/2019/12/06/comparing-equivalent-python-statements/" rel="alternate"></link><updated>2019-12-06T22:19:00+02:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2019-12-06:blog/2019/12/06/comparing-equivalent-python-statements/</id><summary type="html">&lt;p&gt;&lt;img alt="Equivalent Python statements" src="/images/equivalent_python_statements.jpg" title="Equivalent Python statements" /&gt;&lt;/p&gt;
&lt;p&gt;While teaching one of my Python classes yesterday I noticed a conditional expression
which can be written in several ways. All of these are equivalent in their behavior:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;My preferred style of writing is the last one (&lt;code&gt;not os.path.isdir()&lt;/code&gt;) because it
looks the most pythonic of all. However the 5 expressions are slightly different
behind the scenes so they must also have different speed of execution
(click operator for link to documentation):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/reference/expressions.html#is"&gt;&lt;code&gt;is&lt;/code&gt;&lt;/a&gt; - identity operator,
  e.g. both arguments are the same object as determined by the
  &lt;a href="https://docs.python.org/3/library/functions.html#id"&gt;&lt;code&gt;id()&lt;/code&gt;&lt;/a&gt; function. In CPython
  that means both arguments point to the same address in memory&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/reference/expressions.html#is"&gt;&lt;code&gt;is not&lt;/code&gt;&lt;/a&gt; -
  yields the inverse truth value of &lt;code&gt;is&lt;/code&gt;, e.g. both arguments are not the same
  object (address) in memory&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/stdtypes.html#comparisons"&gt;&lt;code&gt;==&lt;/code&gt;&lt;/a&gt; - equality
  operator, e.g. both arguments have the same value&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/stdtypes.html#comparisons"&gt;&lt;code&gt;!=&lt;/code&gt;&lt;/a&gt; - non-equality
  operator, e.g. both arguments have different values&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not"&gt;&lt;code&gt;not&lt;/code&gt;&lt;/a&gt; -
  boolean operator&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In my &lt;a href="https://twitter.com/atodorov_/status/1202504284400750592"&gt;initial tweet&lt;/a&gt; I mentioned
that I think &lt;code&gt;is False&lt;/code&gt; should be the fastest. &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; team
member &lt;a href="https://twitter.com/real_zahari"&gt;Zahari&lt;/a&gt; countered with &lt;code&gt;not&lt;/code&gt; to be the fastest
but didn't provide any reasoning!&lt;/p&gt;
&lt;p&gt;My initial reasoning was as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;is&lt;/code&gt; is essentially comparing addresses in memory so it should be as fast as it gets&lt;/li&gt;
&lt;li&gt;&lt;code&gt;==&lt;/code&gt; and &lt;code&gt;!=&lt;/code&gt; should be roughly the same but they do need to "read" values
  from memory which would take additional time before the actual comparison of
  these values&lt;/li&gt;
&lt;li&gt;&lt;code&gt;not&lt;/code&gt; is a boolean operator but honestly I have no idea how it is implemented
  so I don't have any opinion as to its performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using the following performance test script we get the average of 100 repetitions
from executing the conditional statement 1 million times:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="c"&gt;#!/usr/bin/env python&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;statistics&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;timeit&lt;/span&gt;

&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;if False:&lt;/span&gt;
&lt;span class="sd"&gt;#if not result:&lt;/span&gt;
&lt;span class="sd"&gt;#if result is False:&lt;/span&gt;
&lt;span class="sd"&gt;#if result is not True:&lt;/span&gt;
&lt;span class="sd"&gt;#if result != True:&lt;/span&gt;
&lt;span class="sd"&gt;#if result == False:&lt;/span&gt;
&lt;span class="sd"&gt;    pass&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;import os&lt;/span&gt;
&lt;span class="sd"&gt;result = os.path.isdir(&amp;#39;/tmp&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;execution_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;average_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execution_times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;average_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; in none of these variants the body of the if statement is executed so
the results must be pretty close to how long it takes to calculate the
conditional expression itself!&lt;/p&gt;
&lt;p&gt;Results (ordered by speed of execution):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;False _______ 0.009309015863109380&lt;/code&gt; - baseline&lt;/li&gt;
&lt;li&gt;&lt;code&gt;not result __ 0.011714859132189304&lt;/code&gt; - +25.84%&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is False ____ 0.018575656899483876&lt;/code&gt; - +99.54%&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is not True _ 0.018815848254598680&lt;/code&gt; - +102.1%&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!= True _____ 0.024881873669801280&lt;/code&gt; - +167.2%&lt;/li&gt;
&lt;li&gt;&lt;code&gt;== False ____ 0.026119318689452484&lt;/code&gt; - +180.5%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now these results weren't exactly what I was expecting. I thought &lt;code&gt;not&lt;/code&gt; will come in
last but instead it came in first! Although &lt;code&gt;is False&lt;/code&gt; came in second it is almost
twice as slow compared to baseline. Why is that ?&lt;/p&gt;
&lt;p&gt;After digging around in
&lt;a href="https://github.com/python/cpython"&gt;CPython&lt;/a&gt; I found the following definition
for comparison operators:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;span class="filename"&gt;Python/ceval.c&lt;/span&gt;&lt;pre&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;cmp_outcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;PyCmp_IS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;PyCmp_IS_NOT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... skip PyCmp_IN, PyCmp_NOT_IN, PyCmp_EXC_MATCH ... */&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PyObject_RichCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;Py_True&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Py_False&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;where &lt;code&gt;PyObject_RichCompare&lt;/code&gt; is defined as follows (definition order reversed
in actual sources):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;span class="filename"&gt;Objects/object.c&lt;/span&gt;&lt;pre&gt;&lt;span class="cm"&gt;/* Perform a rich comparison with object result.  This wraps do_richcompare()&lt;/span&gt;
&lt;span class="cm"&gt;   with a check for NULL arguments and a recursion check. */&lt;/span&gt;
&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;PyObject_RichCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Py_LT&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;Py_GE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyErr_Occurred&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;PyErr_BadInternalCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Py_EnterRecursiveCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; in comparison&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;do_richcompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Py_LeaveRecursiveCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* Perform a rich comparison, raising TypeError when the requested comparison&lt;/span&gt;
&lt;span class="cm"&gt;   operator is not supported. */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;do_richcompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;richcmpfunc&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;checked_reverse_op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;PyType_IsSubtype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_richcompare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;checked_reverse_op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Py_SwappedOp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Py_NotImplemented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_richcompare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Py_NotImplemented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;checked_reverse_op&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_richcompare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Py_SwappedOp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;Py_NotImplemented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Py_DECREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**********************************************************************&lt;/span&gt;

&lt;span class="cm"&gt;        IMPORTANT: actual execution enters the next block because the bool&lt;/span&gt;
&lt;span class="cm"&gt;        type doesn&amp;#39;t implement it&amp;#39;s own `tp_richcompare` function, see:&lt;/span&gt;
&lt;span class="cm"&gt;        Objects/boolobject.c PyBool_Type (near the bottom of that file)&lt;/span&gt;

&lt;span class="cm"&gt;    ***********************************************************************/&lt;/span&gt;

    &lt;span class="cm"&gt;/* If neither object implements it, provide a sensible default&lt;/span&gt;
&lt;span class="cm"&gt;       for == and !=, but raise an exception for ordering. */&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;Py_EQ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;Py_True&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Py_False&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nl"&gt;Py_NE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;Py_True&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Py_False&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="s"&gt;&amp;quot;&amp;#39;%s&amp;#39; not supported between instances of &amp;#39;%.100s&amp;#39; and &amp;#39;%.100s&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;opstrings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                     &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;not&lt;/code&gt; operator is defined in &lt;code&gt;Objects/object.c&lt;/code&gt; as follows (definition order
reverse in actual sources):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;span class="filename"&gt;Objects/object.c&lt;/span&gt;&lt;pre&gt;&lt;span class="cm"&gt;/* equivalent of &amp;#39;not v&amp;#39;&lt;/span&gt;
&lt;span class="cm"&gt;   Return -1 if an error occurred */&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;PyObject_Not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PyObject_IsTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* Test a value used as condition, e.g., in a for or if statement.&lt;/span&gt;
&lt;span class="cm"&gt;   Return -1 if an error occurred */&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;PyObject_IsTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Py_ssize_t&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Py_True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Py_False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Py_None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;        IMPORTANT: skip the rest because we are working with bool so this&lt;/span&gt;
&lt;span class="cm"&gt;        function will return after the first or the second if statement!&lt;/span&gt;
&lt;span class="cm"&gt;    */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So a rough overview of calculating the above expressions is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;not&lt;/code&gt; - call 1 function which compares the argument with &lt;code&gt;Py_True/Py_False&lt;/code&gt;,
  compare its result with 0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is/is not&lt;/code&gt; - do a switch/case/break, compare the result to &lt;code&gt;Py_True/Py_False&lt;/code&gt;,
   call 1 function (&lt;code&gt;Py_INCREF&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;==/!=&lt;/code&gt; - switch/default (that is evaluate all case conditions before that),
   call 1 function (&lt;code&gt;PyObject_RichCompare&lt;/code&gt;), which performs couple of
   checks and calls another function (&lt;code&gt;do_richcompare&lt;/code&gt;), which does a few more checks
   before executing switch/case/compare to &lt;code&gt;Py_True/Py_False&lt;/code&gt;, call &lt;code&gt;Py_INCREF&lt;/code&gt;
   and return the result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously &lt;code&gt;not&lt;/code&gt; has the shortest code which needs to be executed.&lt;/p&gt;
&lt;p&gt;We can also invoke the &lt;code&gt;dis&lt;/code&gt; module, aka disassembler of Python byte code into mnemonics
like so (it needs a function to dissasemble):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;dis&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;From the results below you can see that all expression variants are very similar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;--------------- if False -------------------------

              0 LOAD_GLOBAL              0 (False)
              3 POP_JUMP_IF_FALSE        9

              6 JUMP_FORWARD             0 (to 9)
        &amp;gt;&amp;gt;    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE            None
--------------- if not result --------------------
              0 LOAD_FAST                0 (result)
              3 POP_JUMP_IF_TRUE         9

              6 JUMP_FORWARD             0 (to 9)
        &amp;gt;&amp;gt;    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE            None
--------------- if result is False ---------------
              0 LOAD_FAST                0 (result)
              3 LOAD_GLOBAL              0 (False)
              6 COMPARE_OP               8 (is)
              9 POP_JUMP_IF_FALSE       15

             12 JUMP_FORWARD             0 (to 15)
        &amp;gt;&amp;gt;   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE            None
--------------- if result is not True ------------
              0 LOAD_FAST                0 (result)
              3 LOAD_GLOBAL              0 (True)
              6 COMPARE_OP               9 (is not)
              9 POP_JUMP_IF_FALSE       15

             12 JUMP_FORWARD             0 (to 15)
        &amp;gt;&amp;gt;   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE            None
--------------- if result != True ----------------
              0 LOAD_FAST                0 (result)
              3 LOAD_GLOBAL              0 (True)
              6 COMPARE_OP               3 (!=)
              9 POP_JUMP_IF_FALSE       15

             12 JUMP_FORWARD             0 (to 15)
        &amp;gt;&amp;gt;   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE            None
--------------- if result == False ---------------
              0 LOAD_FAST                0 (result)
              3 LOAD_GLOBAL              0 (False)
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_FALSE       15

             12 JUMP_FORWARD             0 (to 15)
        &amp;gt;&amp;gt;   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE            None
--------------------------------------------------
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The last 3 instructions are the same (that is the implicit &lt;code&gt;return None&lt;/code&gt; of the function).
&lt;code&gt;LOAD_GLOBAL&lt;/code&gt; is to "read" the &lt;code&gt;True&lt;/code&gt; or &lt;code&gt;False&lt;/code&gt; boolean constants and
&lt;code&gt;LOAD_FAST&lt;/code&gt; is to "read" the function parameter in this example.
All of them &lt;code&gt;_JUMP_&lt;/code&gt; outside the if statement and the only difference is
which comparison operator is executed (if any in the case of &lt;code&gt;not&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 1:&lt;/strong&gt;
as I was publishing this blog post I read the following comments from
Ammar Askar who also gave me a few pointers on IRC:
&lt;blockquote class="twitter-tweet"&gt;&lt;p lang="en" dir="ltr"&gt;Note that this code path also has a direct inlined check for booleans, which should help too: &lt;a href="https://t.co/YJ0az3q3qu"&gt;https://t.co/YJ0az3q3qu&lt;/a&gt;&lt;/p&gt;&amp;mdash; Ammar Askar (@&lt;strong&gt;ammar2&lt;/strong&gt;) &lt;a href="https://twitter.com/__ammar2__/status/1203012870386139137?ref_src=twsrc%5Etfw"&gt;December 6, 2019&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt; &lt;/p&gt;
&lt;p&gt;So go ahead and take a look at
&lt;a href="https://github.com/python/cpython/blob/e76ee1a72b9e3f5da287663ea3daec4bb3f67612/Python/ceval.c#L2989-L3001"&gt;&lt;code&gt;case TARGET(POP_JUMP_IF_TRUE)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the above comments from Ammar Askar on Twitter and from
Kevin Kofler below I decided to try and change one of the expressions a bit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;result = not result&lt;/span&gt;
&lt;span class="sd"&gt;if result:&lt;/span&gt;
&lt;span class="sd"&gt;    pass&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;import os&lt;/span&gt;
&lt;span class="sd"&gt;result = os.path.isdir(&amp;#39;/tmp&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;that is, calculate the &lt;code&gt;not operation&lt;/code&gt;, assign to variable and then evaluate the
conditional statement in an attempt to bypass the built-in compiler optimization.
The dissasembled code looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;0 LOAD_FAST                0 (result)
2 UNARY_NOT
4 STORE_FAST               0 (result)

6 LOAD_FAST                0 (result)
8 POP_JUMP_IF_FALSE       10
10 LOAD_CONST              0 (None)
12 RETURN_VALUE            None
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The execution time was around &lt;code&gt;0.022&lt;/code&gt; which is between &lt;code&gt;is&lt;/code&gt; and &lt;code&gt;==&lt;/code&gt;. However the
&lt;code&gt;not result&lt;/code&gt; operation itself (without assignment) appears to execute for &lt;code&gt;0.017&lt;/code&gt;
which still makes the &lt;code&gt;not&lt;/code&gt; operator faster than the &lt;code&gt;is&lt;/code&gt; operator, but only just!&lt;/p&gt;
&lt;p&gt;Like already pointed out this is a fairly complex topic and it is evident
that not everything can be compared directly in the same context (expression).&lt;/p&gt;
&lt;h2&gt;P.S.&lt;/h2&gt;
&lt;p&gt;When I teach Python I try to explain what is going on under the hood. Sometimes
I draw squares on the whiteboard to represent various cells in memory and visualize
things. One of my students asked me how do I know all of this? The essentials
(for any programming language) are always documented in its official documentation.
The rest is hacking around in its source code and learning how it works. This is
also what I expect people working with/for me to be doing!&lt;/p&gt;
&lt;p&gt;See you soon and Happy learning!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category><category term="Python"></category></entry><entry><title>How to start solving problems in the QA profession</title><link href="http://atodorov.org/blog/2019/07/29/how-to-start-solving-problems-in-the-qa-profession/" rel="alternate"></link><updated>2019-07-29T10:50:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2019-07-29:blog/2019/07/29/how-to-start-solving-problems-in-the-qa-profession/</id><summary type="html">&lt;p&gt;3 months ago &lt;a href="/blog/categories/adriana/"&gt;Adriana&lt;/a&gt; and I hosted a discussion panel at
&lt;a href="http://qachallengeaccepted.com/"&gt;QA: Challenge Accepted&lt;/a&gt; conference together with
Aleksandar Karamfilov (Pragmatic), Gjore Zaharchev (Seavus, Macedonia) and
Svetoslav Tsenov (Progress Telerik). The recording is available below in mixed
Bulgarian and English languages:&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/2LWRF8moV2A" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;The idea for this was born at the end of the previous year mainly because I was
disappointed by what I was seeing in the local (and a bit of European) QA communities.
In
&lt;a href="https://dev.bg/%D1%80%D0%B0%D0%B7%D0%B3%D0%BE%D0%B2%D0%BE%D1%80-%D1%81-%D0%B5%D0%B2%D0%B3%D0%B5%D0%BD%D0%B8-%D0%BA%D0%BE%D1%81%D1%82%D0%B0%D0%B4%D0%B8%D0%BD%D0%BE%D0%B2-%D0%B7%D0%B0-%D1%80%D0%B0%D0%B1%D0%BE%D1%82/"&gt;this interview&lt;/a&gt;
Evgeni Kostadinov (Athlon) says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I would advise everyone who is now starting into Quality Assurance to display
mastership at work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is something that we value very strongly in the open source world. For example
in &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; we've built a team of people who contribute on
a regular basis, without much material rewards, constantly improve their skills,
show progress and I (as the project leader) am generally happy with their work. OTOH
I do lots of in-house training at companies, mostly teaching programming to testers
(Python &amp;amp; Ruby). Over the last 2 years I've had 30% of people who do fine, 30% of people
who drop out somewhere in the middle and 30% of people who fail very early in the process.
That is 60% failure rate on entry level material and exercises!&lt;/p&gt;
&lt;p&gt;All of this goes to show that there is big disparity between professional testing and the
open source world I live in. And I want to start tackling the problems because I want the
testers in our communities to really become professional in their field so that we can work
on lots more interesting things in the future. Some of the problems that I see are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lack of personal motivation - many people seem comfortable at entry level positions
  and when faced with the challenge to learn or do something new they fail big time&lt;/li&gt;
&lt;li&gt;Using the wrong titles/job positions in the wrong context - calling QA somebody
  who's clearly a tester or calling Senior somebody who barely started their career.
  All of that leads to confusion across the board&lt;/li&gt;
&lt;li&gt;Lack of technical skills, particularly when it comes to programming - how would you
  expect to do software testing if you have no idea how that software is built ?!?
  How are you going to get advantage of new tools and techniques when most of them
  are based around automation and source code ?!?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;I am strong believer that personal motivation is key to everything. However this is also
one of my weakest points. I don't know how to motivate others because I never felt the
need for someone else to motivate me. I don't understand why there could be people who
are seemingly satisfied with a very low hanging fruit when there are so many opportunities
waiting for them. Maybe part of my reasoning is because of my open source background
where DIY is king, where "Talk is cheap. Show me the code." is all that matters.&lt;/p&gt;
&lt;p&gt;Discussion starts with Svetoslav who doesn't
have a technical education/background. He's changed profession later in life and in
recent years has been speaking at some events about testing they do in the
NativeScript team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Svetoslav:&lt;/strong&gt; He realized that he needs to make a change in his life,
invested lots in studying (not just 3 months) all the while traveling between his home town
and Sofia by car and train and still keeping his old job to be able to pay the bills.
He sees the profession not as a lesser field compared to development but as equal.
That is he views himself as an engineer specializing in testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aleksandar:&lt;/strong&gt; There are no objective reasons for some people to be doing very good
in our field while others fail spectacularly. This coming from the owner of one of the
biggest QA academies in the country. A trend he outlines is the folks who come for
knowledge and put their effort into it and the ones who are motivated by the relatively
high salary rates in the industry. In his opinion current practitioners should not
be giving false impression that the profession is easy because there are equally hard
items as in any other engineering field. Wrong impression about how hard/easy it is
to achieve the desired monetary reward is something that often leads to failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gjore:&lt;/strong&gt; Coming from his teaching background at the University of Niš he says people
generally have the false impression they will learn everything by just attending
lectures/training courses and not putting effort at home. I can back this up 100%
judging by performance levels of my corporate students. Junior level folks often
don't understand how much they need to invest into improving their skills especially
in the beginning. OTOH job holders often don't want to listen to others because they
think they know it all already. Another field he's been experimenting with is a
mentoring program.&lt;/p&gt;
&lt;h2&gt;Tester, QA, QE, etc - which is what and why that matters&lt;/h2&gt;
&lt;p&gt;IMO part of the problem is that we use different words to often describe the same thing.
Companies, HR, employees and even I are guilty of this. We use various terms
interchangeably while they have subtle but important differences.&lt;/p&gt;
&lt;p&gt;As a friend of mine told me&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;even if you write automation all the time if you do it after the fact
(e.g. after a bug was reported) then you are not QA/QE - you are a simple tester
(with a slightly negative connotation)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Aleksandar:&lt;/strong&gt; terminology has been defined long time ago but the problem comes from
job offers which use the wrong titles (to make the position sound sexier). Another
problem is the fact that Bulgaria (also Macedonia, Serbia and I dare say Romania) are
predominantly outsourcing destinations: your employer really needs testers but fierce
competition, lack of skilled people (and distorted markets), etc leads to distortion
in job definitions. He's blaming companies that they don't listen enough to their
employees. &lt;/p&gt;
&lt;p&gt;Note: there's nothing bad in being "just a tester" executing test scenarios and reporting
bugs. That was one of the happiest moments in my career. However you need to be aware of
where you stand, what is required from you and how you would like to develop in the future.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Svetoslav:&lt;/strong&gt; Doesn't really know all the meaning of all abbreviations and honestly
doesn't really care. His team is essentially a DevOps team with lots of mixed responsibility
which necessitates mixed technical and product domain skills. Note that Progress is by
contrast a product company, which is also the field I've always been working in. That is
to be successful in a product company you do need to be a little bit of everything
at different times so the definition of quality engineer gets stretched and skewed
a lot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gjore:&lt;/strong&gt; He's mostly blaming middle level management b/c they do not posses
all the necessary technical skills and don't understand very well the nature of
technical work. In outsourcing environment often people get hired just to
provide head count for the customer, not because they are needed. Software testing
is relatively new on the Balkans and lots of people still have no idea
what to do and how to do it. We as engineers are often silent and contribute to
these issues by not raising them when needed. We're also guilty of not
following some established processes, for example not
attending some required meetings (like feature planning) and by doing so
not helping to improve the overall working process. IOW we're not always
professional enough.&lt;/p&gt;
&lt;h2&gt;Testers and programming&lt;/h2&gt;
&lt;blockquote class="twitter-tweet"&gt;
    &lt;p lang="en" dir="ltr"&gt;Testers should be code literate. Reading code is a crucial skill for any tester and writing code has so many uses beyond just boilerplate automation.
        &lt;a href="https://t.co/Tts0rzHI4Y"&gt;https://t.co/Tts0rzHI4Y
    &lt;/a&gt;&lt;/p&gt;&amp;mdash; Amber Race (@ambertests)
    &lt;a href="https://twitter.com/ambertests/status/1109862132554694656?ref_src=twsrc%5Etfw"&gt;March 24, 2019&lt;/a&gt;
&lt;/blockquote&gt;

&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

&lt;p&gt;On one of my latest projects we've burned through
the following technologies in the span of 1 year: Rust, Haskell, Python, React, all sorts
of cloud vendors (pretty much all of them) and Ansible of course. Testing was adjusted
as necessary and while hiring we only ask for the person to have adequate coding
skills in Python, Bash or any other language. The rest they have to learn accordingly.&lt;/p&gt;
&lt;p&gt;So what to do about it? My view is that anyone can learn programming but not many
people do it successfully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Svetoslav:&lt;/strong&gt; To become an irreplaceable test engineer you need skills. Broad technical
skills are a must and valued very highly. This is a fact, not a myth. Information is
easily accessible so there's really no excuse not to learn. Mix in product and business
domain knowledge and you are golden.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aleksandar:&lt;/strong&gt; Everyone looks like they wish to postpone learning something new, especially
programming. Maybe because it looks hard (and it is), maybe because people don't feel
comfortable in the subject, maybe because they haven't had somebody to help them
and explain to them critical concepts. OTOH having all of that technical understanding
actually makes it easier to test software b/c you know how it is built and how it works.
Sometimes the easiest way to explain something is by showing its source code (I do this a lot).&lt;/p&gt;
&lt;p&gt;Advice to senior folks: don't troll people who have no idea about something they've
never learned before. Instead try to explain it to them, even if they don't want to hear it.
This is the only way to help them learn and build skills. In other words: be a good
team player and help your less fortunate coworkers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gjore:&lt;/strong&gt; A must have is to know the basic principles of
&lt;a href="https://en.wikipedia.org/wiki/Object-oriented_programming"&gt;object oriented programming&lt;/a&gt;
and I would add also &lt;a href="https://en.wikipedia.org/wiki/SOLID"&gt;SOLID&lt;/a&gt;. With the ever changing
landscape of requirements towards our profession we're either into the process of change
or out of this process.&lt;/p&gt;
&lt;h2&gt;Summary and action items&lt;/h2&gt;
&lt;p&gt;The software testing industry is changing. All kind of requirements are pushing our
profession outside its comfort zone, often outside of what we signed up for initially.
This is a fact necessitated by evolving business needs and competition. This is equally
true for product and outsourcing companies (which work for product companies after all).
This is equally true for start-ups, SME and big enterprises.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QA shifting left and right" src="/images/qa_shift_left_right.png" title="QA shifting left and right" /&gt;
Image from &lt;a href="https://www.youtube.com/watch?v=jFZd6MaKKZg"&gt;No Country for Old QA, Emanuil Slavov (Komfo)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What can we do about it ?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Svetoslav:&lt;/strong&gt; Invest in building an awesome (technical) team. Make it a challenge to
learn and help your team mates to learn with you. However be frank with yourself and with
them. Ask for help if you don't know something. Don't be afraid to help other people
level-up because this will ultimately lead to you leveling-up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aleksandar:&lt;/strong&gt; Industry should start investing in improving workers qualification level
because Bulgaria is becoming an expensive destination. We're on-par with some companies
in western Europe and USA (coming from a person who also sells the testing service).
Without raising skills level we're not going to have anything competitive to offer.
Also pay attention to building an inclusive culture especially towards people on the
lowest level in terms of skills, job position, responsibilities, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gjore:&lt;/strong&gt; Be the change, drive the change, otherwise it is not going to happen!&lt;/p&gt;
&lt;p&gt;So here are my tips and tricks the way I understand them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Find your motivation and make sure it is the "correct" one - there's nothing
  wrong in wanting a higher salary but make sure you are clear that you are
  trading in your time and knowledge for that. Knowing what's in it for you will
  help you self motivate and pull yourself through hard times&lt;/li&gt;
&lt;li&gt;Find a mentor if possible - I've never had one so I can't offer much advise here&lt;/li&gt;
&lt;li&gt;Software testing is hard, no kidding. Some researchers claim it is even harder
  than software development because the field of testing encompasses the entire field
  of development&lt;/li&gt;
&lt;li&gt;Once you understand the concepts and how things work it becomes easy. We do have
  very fast rate of technology change but most of the things are not fundamental
  paradigm change. Building on this basic knowledge makes things easier (or to put it
  mildly: everything has been invented by IBM in the 1970s)&lt;/li&gt;
&lt;li&gt;You will not learn everything (not even close) in a short course. I've spent 5 years
  in engineering university learning how software and hardware works. I've been
  programming for the past 20 years every single day. This makes it easier but
  there are lots of things I have not idea about. 30-60 minutes of targeted learning
  and applying what you learn goes a long way over the course of many years&lt;/li&gt;
&lt;li&gt;Invest in yourself, nobody is going to do it for you. If you look at
  &lt;a href="https://github.com/atodorov"&gt;github.com/atodorov&lt;/a&gt; you will notice that everything
  is green. If you drill down by year you will find this is the case for the past
  3-4 years only. The 10 years before that I've spent building up to this moment.
  It is only now that I get to reap some of the benefits of doing so (like a random
  Silicon Valley startup telling me they are fans of my work or being invited as
  a speaker at events)&lt;/li&gt;
&lt;li&gt;Programming is hard, when you don't know the basic concepts and when you lack
  the framework to think about abstractions (loops, conditionals, etc). When you
  learn all of this it becomes harder because you need to learn different languages
  and frameworks. However it is not impossible. There are lots of free materials
  available online, now more than ever&lt;/li&gt;
&lt;li&gt;Think about your "position" in the team/company. What do you do, what is required
  of you, how can you do it better ? Call things with their real names and
  explain to your coworkers which is what. This will bring more consistency in the
  entire community&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lots of these items sound cliche but they are true. There's nothing stopping you from
becoming the best QA engineer in the world but you.&lt;/p&gt;
&lt;h2&gt;To be continued&lt;/h2&gt;
&lt;p&gt;This first discussion was born out of necessity and is barely scratching the surface.
The format is not ideal. We didn't present multiple points of view.
We didn't have time to prepare for it to be honest!&lt;/p&gt;
&lt;p&gt;Gjore and I made a promise to continue the discussion bringing it to Macedonia and Serbia.
I am hoping we can also bring other neighboring countries like Romania and Greece on board
and learn from mutual experience.&lt;/p&gt;
&lt;p&gt;See you soon and Happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>Contributing to Open Source with Docker, Inc</title><link href="http://atodorov.org/blog/2019/05/31/contributing-to-open-source-with-docker-inc/" rel="alternate"></link><updated>2019-05-31T12:50:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2019-05-31:blog/2019/05/31/contributing-to-open-source-with-docker-inc/</id><summary type="html">&lt;p&gt;&lt;img alt="Contributing to OSS" src="/images/docker_oss.jpg" title="Contributing to OSS" /&gt;&lt;/p&gt;
&lt;p&gt;The rumors have finally been confirmed. Docker, Inc. is opening their new
R&amp;amp;D center in Sofia. At an event last night, they stated their intentions to do a fair amount
of product development in Sofia as well as contribute to the local society/community
too (if I got this correctly). This is very good news for the local eco-system so
congrats for that from my side!&lt;/p&gt;
&lt;p&gt;This blog post outlines my impressions from the event and a few related more general
thoughts.&lt;/p&gt;
&lt;h2&gt;How did Docker came to Sofia?&lt;/h2&gt;
&lt;p&gt;I don't know the details but their top team in Bulgaria seems to be coming
directly from VMware. So were other engineers present at the event who are
based elsewhere. When you think about it this is not surprising at all.
(FTR VMware is also directly responsible for having Uber engineering in Sofia).&lt;/p&gt;
&lt;p&gt;VMware is one of the few companies in Bulgaria that does real product development
and R&amp;amp;D (credit where credit is due).
There's even a smaller number of companies developing infrastructure
products, e.g. the same things I test on behalf of Red Hat. The majority of the
other companies are either outsourcing or focused on products in upper layers
of the stack!&lt;/p&gt;
&lt;h2&gt;Contributing to Open Source according to Docker (and myself)&lt;/h2&gt;
&lt;p&gt;This BoF session was lead by Andrew Hsu and Sebastiaan van Stijn.
The group was predominantly inexperienced in terms of OSS contribution but
motivated to try/find a project where they can contribute. From what I could
tell they were relatively experienced software engineers.&lt;/p&gt;
&lt;p&gt;On my question "what are they planning for the local community in Sofia?" the
immediate answer was meet-ups and presentations which is expected. This is how
you start and try to establish the level of experience of the local groups
and their level of interest in what you are doing.&lt;/p&gt;
&lt;p&gt;I prompted a bit further about workshops or hackathons and they told me
they've had a hacking even in Paris but didn't elaborate much further. Maybe
it is too early for them to be able to give more detailed answers.
Let's hope we'll see more practical events.&lt;/p&gt;
&lt;p&gt;Andrew did outline the general principles of their community (aka don't be a jerk),
pointed out the various communication channels they have (rip IRC), the fact that
internally the company uses GitHub and encourages cross-team participation via
the &lt;a href="https://gist.github.com/Chaser324/ce0505fbed06b947d962"&gt;pull request workflow&lt;/a&gt;.
This is what my friends at &lt;a href="https://bitergia.com/"&gt;Bitergia&lt;/a&gt; call "inner source"
and is a good thing!&lt;/p&gt;
&lt;p&gt;A few of the participants asked how and where to start and all I kept hearing was
"follow pull requests on GitHub", "do you want to see some source code"! This is
something I take issue with so let me explain.&lt;/p&gt;
&lt;p&gt;While that has been the historical model for doing open source, aka dig straight
into the problem, and also how I started and still do open source I think it doesn't
work in the modern world. What I've seen from my students and folks which I've trained
is that they have far too many opportunities to be bothered to dig deep into something
which seemingly puts roadblocks on every step of the way. Especially when you are
a new comer. I myself experience this regularly and often get frustrated by
communities who make it damn nearly impossible to land a code change. My only motivation
here is that I depend on that component being fixed and there is no work-around.&lt;/p&gt;
&lt;p&gt;To be fair to Docker our Fedora or Red Hat communities aren't much better in this regard.
In most of the projects I've contributed they kind of expect you to be motivated
enough and be able to figure out both process and technical details mostly on your own!
Maybe it is the nature of working on platform and infrastructure. You do need a fair bit
of general knowledge and specific system knowledge to work on such projects.&lt;/p&gt;
&lt;p&gt;My personal experience leading &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; has been that most
contributors need a long time to settle in and feel comfortable in the project and
that they do need a fair amount of hand holding.&lt;/p&gt;
&lt;p&gt;First and fore-most many contributors don't know the underlying technology well enough.
For complex software there's also the whole issue of computer science 101, operating systems,
how the kernel or virtualization engine works, etc. Then you need to know the architecture
of the software you want to fix, the libraries and frameworks it uses - this helps you
quickly navigate to the place which needs a patch. Honestly this takes years to master
and to develop a gut feeling about it. On the outside it may look easy because active
contributors have had many years of experience acquiring this knowledge.&lt;/p&gt;
&lt;p&gt;Then we have the "process" part. How do I open or rebase a pull request. How to amend
commits, etc. This is something I learned the hard way but I've shown it to other
people and they were able to advance much more quickly. Also things like how do you communicate
with others in the community, how do you "push" for some types of changes, etc.
Dedicated mentors will help a lots here, but that also means dedicated contributors.&lt;/p&gt;
&lt;p&gt;We do provide a detailed technical training
and on-boarding program and mentoring for Kiwi TCMS and still there are more people who give it a try
and drop out compared to those who stay with the team.
We still expect commitment and finishing the tasks one set out to complete though.&lt;/p&gt;
&lt;p&gt;My initial impression (from Docker) for the moment is very guarded and mostly critical.
I feel like they are interested in finding folks to contribute to their own repositories
and then hire them (that is expected) but I don't feel like they care much about what
happens outside their own projects. I hope I am wrong and we do see engineers (regardless
of who employs them) contributing all over the place on a regular basis.&lt;/p&gt;
&lt;h2&gt;What is the problem ?&lt;/h2&gt;
&lt;p&gt;The problem for the local eco-system (and it is a world wide problem)
is that there are many companies coming in but there is a very limited pool of talent.
Especially in less popular fields like research, operating systems and low level infrastructure.
That takes many years to develop in house and to reach critical mass for a thriving
community. I don't feel we are there yet!&lt;/p&gt;
&lt;p&gt;In a later blog post I will describe the history of ScyllaDB which
is the measure of success I would like to see in Bulgaria.&lt;/p&gt;
&lt;p&gt;The problem I see for the open source community (in the country) is that nobody is really
working on developing that. There are small efforts by individuals or a few companies but
the mechanics of open source and the culture of free sharing of knowledge is something
I don't see yet. I fail to see a program, like Google Summer of Code perhaps, where
developers are encouraged and supported to contribute just for the sake of contributing.&lt;/p&gt;
&lt;p&gt;Also I fail to see a structure which will help new contributors and
young developers set out on a path of meaningful contributions early in their career
and by doing this improve their skills and personal brand which ties in with the first
paragraph.&lt;/p&gt;
&lt;p&gt;These are some things I have observed and some gut feelings from someone who's been doing
open source for 15+ years. I can't pin point exact reason why this is happening.
I don't have a recipe how to fix it!&lt;/p&gt;
&lt;p&gt;I do however keep in touch with like minded folks from several other companies and we've
discussed these topics occasionally. We do have some ideas but lack critical mass,
shared goal and self-organization.&lt;/p&gt;</summary><category term="fedora.planet"></category></entry><entry><title>The Art of [Unit] Testing</title><link href="http://atodorov.org/blog/2019/04/05/the-art-of-unit-testing/" rel="alternate"></link><updated>2019-04-05T17:10:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2019-04-05:blog/2019/04/05/the-art-of-unit-testing/</id><summary type="html">&lt;p&gt;A month ago I held a private discussional workshop for a friend's company in Sofia.
With people at executive positions on the tech &amp;amp; business side we discussed
some of their current problems with respect to delivering a quality product.
Additionally I had a list of pre-compiled questions from members of the technical team,
young developers, mostly without formal background in software testing!
Some of the answers were inspired by
&lt;a href="https://amzn.to/2VhvXox"&gt;The Art of Unit Testing by Roy Osherove&lt;/a&gt; hence the title!&lt;/p&gt;
&lt;h2&gt;Questions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Types of testing, general classification&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are
&lt;a href="https://www.softwaretestinghelp.com/types-of-software-testing/"&gt;many types of testing&lt;/a&gt;!
Unit, Integration, System, Performance and Load, Mutation, Security, etc. Between
different projects we may use the same term to refer to slightly different types
of testing.&lt;/p&gt;
&lt;p&gt;For example in &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; we generally test with a database deployed,
hit the application through its views (backend points that serve HTTP requests) and assert
on the response of these functions. The entire request-response cycle goes through the
application together with all of its settings and add-ons! In this project we are
more likely to classify this type of testing as Integration testing although at times
it is more closer to System testing.&lt;/p&gt;
&lt;p&gt;The reason I think Kiwi TCMS is more closer to integration testing is because we execute
the tests against a running development version of the application! The test runner process
and the SUT process are in the same memory space (different threads sometimes).
In contrast full system testing for Kiwi TCMS will mean building and deploying the docker
container (a docker compose actually), hitting the application through the layer
exposed by Docker and asserting on the results. Here test runner and SUT are two distinctly
separate processes. Here we also have email integration, GitHub and Bugzilla integration,
additional 3rd party libraries that are installed in the Docker imaga, e.g. kerberos
authentication.&lt;/p&gt;
&lt;p&gt;In another example for
&lt;a href="https://github.com/MrSenko/pelican-ab/tree/master/tests"&gt;pelican-ab&lt;/a&gt; we mostly have unit
tests which show the SUT as working. However pelican-ab for a static HTML generator
and if failed miserably with &lt;code&gt;DELETE_OUTPUT_DIRECTORY=True&lt;/code&gt; setting! The problem here is that
&lt;code&gt;DELETE_OUTPUT_DIRECTORY&lt;/code&gt; doesn't control anything in the SUT but does control
behavior in the outer software! This can only be detected with integration tests,
where we perform &lt;em&gt;testing of all integrated modules to verify the combined functionality&lt;/em&gt;,
see &lt;a href="http://atodorov.org/blog/2016/12/27/mutation-testing-vs-coverage/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As we don't depend on other services like a database I will classify this as pure integration
testing b/c we are testing a plugin + specific configuration of the larger system which enforces more
constraints.&lt;/p&gt;
&lt;p&gt;My best advice is to:&lt;/p&gt;
&lt;p&gt;1) have a general understanding of what the different terms mean in the industry
2) have a consensus within your team what do you mean when you say &lt;em&gt;X type of testing&lt;/em&gt;
   and &lt;em&gt;Y type of testing&lt;/em&gt; so that all of you speak the same language
3) try to speak a language which is closest to what the rest of the industry does,
   baring in mind that we people abuse and misuse language all the time!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is unit testing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The classical definition is&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A unit test is a piece of code (usually a method) that invokes another piece of code
and checks the correctness of some assumptions afterwards. If the assumptions turn out
to be wrong the unit test has failed.
&lt;strong&gt;A unit is a method or function&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Notice the emphasis above: a unit is method or a function - we exercise these in unit tests.
We should be examining their results or in a worse case the state of the class/module
which contains these methods! Now also notice that this definition is different from the
one available in the link above. For reference it is&lt;/p&gt;
&lt;blockquote&gt;
&lt;h1&gt;42) Unit Testing&lt;/h1&gt;
&lt;p&gt;Testing of an individual software component or module is termed as Unit Testing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Component&lt;/em&gt; can be a single class which comes close to the definition for unit testing but
it can be several different classes, e.g. an authentication component handling several different
scenarios. Modules in the sense of modules in a programming language almost always contain
multiple classes and methods! Thus we unit test the classes and methods but we can rarely
speak about &lt;em&gt;unit testing&lt;/em&gt; the module itself.&lt;/p&gt;
&lt;p&gt;OTOH the second definition gets the following correctly:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is typically done by the programmer and not by testers, as it requires a detailed
knowledge of the internal program design and code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my world, where everything is open source we testers can learn how the SUT and its
classes and methods work and we can also write pure unit tests. For example in
&lt;a href="https://github.com/weldr/codec-rpm/commit/308c083afbe6f2f2ba64c83433d6a0262a5ab44c"&gt;codec-rpm&lt;/a&gt;
I had the pleasure to add very pure unit tests - call a function and assert on its result,
nothing else in the system state changed (that's how the software was designed to work)!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Next questions ask about &lt;em&gt;how to ... unit test ...&lt;/em&gt; and the term "unit test" in them is
used wrongly! I will drop this and only use "test" to answer!&lt;/p&gt;
&lt;p&gt;Also important - &lt;strong&gt;make the difference between unit type test and another type of
test written with a unit testing framework&lt;/strong&gt;! In most popular programming languages unit
testing frameworks are very powerful! They can automatically discover your test suite (discovery),
execute it (test runner), provide tooling for asserting conditions (equal, not equal, True,
has changed, etc) and tooling for reporting on the results (console log, HTML, etc).&lt;/p&gt;
&lt;p&gt;For example Kiwi TCMS is a Django application and it uses the standard test framework
from Django which derives from Python's unittest! A tester can use pretty much any kind
of testing framework to automate pretty much any kind of test! Some frameworks just make
particular types of tests easier to implement than others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to write our tests without touching the DB when almost all business logic is
contained within Active Record objects? Do we have to move this logic outside Active Record,
in pure PHP classes that don't touch DB?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To answer the second part - it doesn't really matter. Separating logic from database is
a nicer design in general (loosely coupled) but not always feasible. Wrt testing you can either
mock calls to the database or perform your tests with the DB present.&lt;/p&gt;
&lt;p&gt;For example Kiwi TCMS is a DB heavy applcation. Everything comes and goes to the
database, it hardly has any stand-alone logic. Thus the most natural way to test is together
with the database! Our framework provides tooling to load previously prepared test data
(db migrations, fixtures) and we also use &lt;code&gt;factoryboy&lt;/code&gt; to speed up creation of ORM objects
only with the specific attributes that we need for the test!&lt;/p&gt;
&lt;p&gt;Key here is speed and ease of development, not what is the best way in theory! In real-life
testing there are hardly any best practices IMO. Testing is always very context dependent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it good to test methods with Eloquent ORM/SQL statements and how to do it without a database?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://laravel.com/docs/5.7/eloquent"&gt;Eloquent&lt;/a&gt; is the ORM layer for Laravel thus the question
becomes the same as the previous one.! When the application is dependent on the DB, which in their
case is, then it makes sense to use a database during testing!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For Feature tests isn't it better to to test them without a DB and b/c we have more business
logic there. For them we must be certain that we call the correct methods?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Again, same as the previous one. Use the database when you have to! And two questions:&lt;/p&gt;
&lt;p&gt;1) Does the database messes your testing up in some way? Does it prevent you from doing something?
   If yes, just debug the hell out of it, figure out what happens and then figure out how to
   fix it
2) What on Earth is &lt;em&gt;we must be certain that we call the correct methods&lt;/em&gt; mean? (I am writing this
   as personal notes before the actual session took place). I suspect that this is the more general
   &lt;em&gt;am I testing for the right thing&lt;/em&gt; question which inexperienced engineers ask. My rule of thumb
   is: check what do you assert on. Are you asserting that the record was created in the DB (so verifying
   explicitly permissions, DB setup, ORM correctness) or that the result of the operation mathes what
   the business logic expects (so verifying explicitly the expected behavior and implicitly that all
   the layers below managed to work so the data was actually written to disk)? At times both may be
   necessary (e.g. large system, lots of cachine, eventual consistency) but more often than not
   we need to actually assert on the business logic.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;technical validation: user tries to register an account, assert email was sent or&lt;/li&gt;
&lt;li&gt;business/behavior validation: user tries to register an account, after confirming their intent
  they are able to login&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Optimization for faster execution time, parallel execution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Parallel testing is no, no, no in my book! If you do not understand why something is slow
trowing more instances at it increases your complexity and decreases the things you do
understand and subsequently are able to control and modify!&lt;/p&gt;
&lt;p&gt;Check-out this excellent presentation by
&lt;a href="https://www.youtube.com/watch?v=hbocBqOpuAo#t=3h18m25s"&gt;Emanuil Slavov&lt;/a&gt; at
GTAC 2016. The most important thing Emanuil says is that a fast test suite is the result of many
conscious actions which introduced small improvements over time. His team had assigned
themselves the task to iteratively improve their test suite performance and at every step
of the way they analyzed the existing bottlenecks and experimented with possible solutions.&lt;/p&gt;
&lt;p&gt;The steps in particular are (on a single machine):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Execute tests in dedicated environment;&lt;/li&gt;
&lt;li&gt;Start with empty database, not used by anything else; This also leads to
adjustments in your test suite architecture and DB setup procedures;&lt;/li&gt;
&lt;li&gt;Simulate and stub external dependencies like 3rd party services;&lt;/li&gt;
&lt;li&gt;Move to containers but beware of slow disk I/O;&lt;/li&gt;
&lt;li&gt;Run database in memory not on disk because it is a temporary DB anyway;&lt;/li&gt;
&lt;li&gt;Don't clean test data, just trash the entire DB once you're done; Will also require
  adjustments to tests, e.g. assert the actual object is there, not that there are
  now 2 objects;&lt;/li&gt;
&lt;li&gt;Execute tests in parallel which should be the last thing to do!&lt;/li&gt;
&lt;li&gt;Equalize workload between parallel threads for optimal performance;&lt;/li&gt;
&lt;li&gt;Upgrade the hardware (RAM, CPU) aka vertical scaling; I would move this before
  parallel execution b/c test systems usually have less resources;&lt;/li&gt;
&lt;li&gt;Add horizontal scaling (probably with a messaging layer);&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are other more heuristical approaches like not running certain tests on
certain branches and/or using historical data to predict what and where to execute.
If you want to be fancy couple this with an ML algorithm but beware that
there are only so many companies in the world that will have any real benefit from this.
You and I probably won't. Read more about &lt;a href="http://atodorov.org/blog/2016/11/30/highlights-from-ista-and-gtac-2016/"&gt;GTAC 2016&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing when touching the file system or working with 3rd party cloud providers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If touching the filesystem is occasional and doesn't slow you down ignore it!
But also make sure you do have a fast disk, this is also true for DB access.
Try to push everything to memory, e.g. large DB buffers, filesystem mounted in memory,
all of this is very easy in Linux. Presumption here is that these are temporary objects
and you will destroy them after testing.&lt;/p&gt;
&lt;p&gt;Now if the actual behavior that you want to test is working with a filesystem (e.g.
producing files on disk) or uploading files to a cloud provider there isn't much you
can do about it! This is a system type of test where you rely on &lt;em&gt;integration&lt;/em&gt; with
a 3rd party solution.&lt;/p&gt;
&lt;p&gt;For example for &lt;a href="https://github.com/atodorov/django-s3-cache"&gt;django-s3-cache&lt;/a&gt;
you need to provide your Amazon S3 authentication tokens before you can execute
the test suite. It will comminicate back and forth with AWS and possibly leave some
artifacts there when it is done!&lt;/p&gt;
&lt;p&gt;Same thing for &lt;a href="https://github.com/weldr/lorax/pull/584"&gt;lorax&lt;/a&gt;, where the essence
of the SUT is to build Linux images ready to be deployed in the cloud! Checkout the
PR above and click the &lt;code&gt;View details&lt;/code&gt; button at the bottom right to see the various
test statuses for this PR:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Travis CI - pylint + unit test + some integration type tests (cli talks to API server)&lt;/li&gt;
&lt;li&gt;very basic sanity tests (invoking the application cli via bash scripts). This hits
  the network to refresh with RPM package data from Fedora/CentOS repositories.&lt;/li&gt;
&lt;li&gt;Jenkins jobs for AWS, Azure, OpenStack, Vmware, other (tar, Docker, stand-alone KVM).
  These will run the SUT, get busy for about 10 minutes to compose a cloud image of the
  chosen format, extract the file to a local directory, upload to the chosen cloud vendor,
  spin up a VM there and wait for it to initialize, ssh to the VM and perform final
  assertions, e.g. validating &lt;em&gt;it was able to boot as we expected it to&lt;/em&gt;. This is for
  x86_64 and we need it for Power, s390x and ARM as well! I am having troubles even finding
  vendors that support all of these environments! Future releases will support even more
  cloud environments so rinse and repeat!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My point is when your core functionality depends on a 3rd party provider your testing will
depend on that as well. In the above example I've had the scenario where VMs in Azure were
taking around 1hr to boot up. At the time we didn't know if that was due to us not integrating
with Azure properly (they don't use cloud-init/NetworkManager but their own code which we
had to install and configure inside the resulting Linux image) or because of infrastructure
issues. It turned out Azure was having networking trouble at the time when our team
was performing final testing before an important milestone. Sigh!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With what tests (Feature or Unit) should I start before refactoring?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So you know you are going to refactor something but it doesn't have [enough] tests?
How do you start? The answer will ellude most developers. You do not start by defining
the types of testing you should implement. You start with analyzing the existing behavior:
how it works, what conditions it expects, what input data, what constraints, etc. This is
very close to black-box testing techniques like decision tables, equivalence partitioning, etc
with the added bonus that you have access to the source code and can more accurately
figure out what is the actual behavior.&lt;/p&gt;
&lt;p&gt;Then you write test scenarios (Given-When-Then or Actions 1, 2, 3 + expected results).
You evaluate these scenarios if they encompass all the previously identified behavior
and classify the risk assiciated with them. What if Scenario X fails after refactoring?
Cloud be the code is wrong, could be the scenario is incomplete. How does that affect
schedule, user experience, business risk (often money), etc.&lt;/p&gt;
&lt;p&gt;Above is tipically the job of a true tester as illustrated by this picture from
Ingo Philipp, full presentation
&lt;a href="https://assets.ctfassets.net/ut4a3ciohj8i/4ukPUn6tfiig8S4ASuaeoQ/670bba8e5498239a7fbbf404952beb08/Ingo_Philipp_Rediscover_Exploratory_Testing.pdf"&gt;here&lt;/a&gt;
&lt;img alt="'What is testing'" src="https://raw.githubusercontent.com/atodorov/qa-automation-ruby-101/master/module00/testing_knowledge_gap.png" /&gt;&lt;/p&gt;
&lt;p&gt;Then and only then you sit down and figure out what types of tests are needed to
automate the identified scenarios, implement them and start refactoring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are inexperienced developers missing most often when writing tests?
How to make my life easier if I am inexperienced and just starting with testing?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;See the picture above! Developers, even experienced ones have a different mind set
when they are working on fixing code or adding new features. What I've seen most oftenly is
adding tests only for happy paths/positive scenarios and not spending enough time to
evaluate and exercise all of the edge cases.&lt;/p&gt;
&lt;p&gt;True 100% test coverage is impossible in practice and there are so many things that can
go wrong. Developers are typically not aware of all that because it is tipically not their
job to do it.&lt;/p&gt;
&lt;p&gt;Also testing and development require different frame of mind. I myself am a tester but I do
have formal education in software engineering and regularly contribute as developer to various
projects (2000+ GitHub conributions as of late). When I revisit some tests I've written
I often find they are pointless and incorrect. This is because at the time I've been
thinking "how to make it work", not "how to test it and validate it actually works".&lt;/p&gt;
&lt;p&gt;For an engineer without lots of experience in testing I would recommend to always start
with a BDD exercise. The reason is it will put you in a frame of mind to think about
expected behavior from the SUT and not think about implementation. This is the basis
for asking questions and defining good scenarios. Automation testing is a means of
expression, not a tool to find a solution to the testing problem!&lt;/p&gt;
&lt;p&gt;Check-out &lt;a href="http://atodorov.org/blog/2016/03/11/qa-switch-from-waterfall-to-bdd/"&gt;this BDD experiment I did&lt;/a&gt;
and also the resources
&lt;a href="https://github.com/atodorov/qa-automation-ruby-101/tree/master/module06"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inside-out(Classi approach) vs Outside-in(Mockist approach)? When and why?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These are terms associated with test driven development (TDD). A quick search reveals
&lt;a href="https://8thlight.com/blog/georgina-mcfadyen/2016/06/27/inside-out-tdd-vs-outside-in.html"&gt;an excellent article explaining this question&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Inside Out TDD allows the developer to focus on one thing at a time.
Each entity (i.e. an individual module or single class) is created until the whole
application is built up. In one sense the individual entities could be deemed
worthless until they are working together, and wiring the system together at a
late stage may constitute higher risk. On the other hand, focussing on one entity at a time
helps parallelise development work within a team.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This sounds to me is more suitable for less experienced teams but does require a strong
senior personel to control the deliverables and steer work in the right direction.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Outside In TDD lends itself well to having a definable route through the system from the
very start, even if some parts are initially hardcoded.
The tests are based upon user-requested scenarios, and entities are wired together from
the beginning. This allows a fluent API to emerge and integration is proved from the start of development.
By focussing on a complete flow through the system from the start, knowledge of how different
parts of the system interact with each other is required. As entities emerge,
they are mocked or stubbed out, which allows their detail to be deferred until later.
This approach means the developer needs to know how to test interactions up front, either through
a mocking framework or by writing their own test doubles. The developer will then loop back,
providing the real implementation of the mocked or stubbed entities through new unit tests.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've seen this in practice in &lt;a href="https://github.com/weldr/welder-web"&gt;welder-web&lt;/a&gt;. This is the
web UI for the above mentioned cloud image builder. The application was developed iteratively
over the past 2 years and initially many of the screens and widgets were hard-coded.
Some of the interactions were not even existing, you click on a button and it does nothing.&lt;/p&gt;
&lt;p&gt;This is more of an MVP, start-up approach, very convenient for frequent product demos
where you can demonstrate that some part of the system is now working and it shows
real data!&lt;/p&gt;
&lt;p&gt;However this requires a relatively experienced team both testers and developers
and relatively well defined product vision. Individual steps (screens, interactions, components)
may not be so well defined but everybody needs to know where the product should go
so we can adjust our work and snap together.&lt;/p&gt;
&lt;p&gt;As everything in testing the real answer is &lt;em&gt;it depends&lt;/em&gt; and is often a mixture of the two.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is the difference between a double, stub, mock, fake and spy?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These are classic unit testing terms defined by Gerard Meszaros in his book
&lt;a href="https://amzn.to/2GYSse5"&gt;xUnit Test Patterns&lt;/a&gt;, more precisely in
&lt;a href="http://xunitpatterns.com/Test%20Double%20Patterns.html"&gt;Test Double Patterns&lt;/a&gt;.
These terms are somewhat confusing and also used interchangeably in testing frameworks
so see below.&lt;/p&gt;
&lt;p&gt;Background:&lt;/p&gt;
&lt;p&gt;In most real-life software we have dependencies:
on other libraries, on filesystems, on database, on external API, on another class
(private and protected methods), etc.
Pure unit testing (see definition at the top) is not concerned with these because we
can't control them. Anytime we cross outside the class under test
(where the method which is unit tested is defined) we have a dependency that
we need to deal with. This may also apply to integration type tests, e.g. I don't want
to hit GitHub every time I want to test my code will not crash when we receive a
response from them.&lt;/p&gt;
&lt;p&gt;From &lt;em&gt;xUnit Test Patterns&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For testing purposes we replace the real dependent component (DOC) with our &lt;strong&gt;Test Double&lt;/strong&gt;.
Depending on the kind of test we are executing, we may hard-code the behavior of the Test Double
or we may configure it during the setup phase. When the SUT interacts with the Test Double,
it won't be aware that it isn't talking to the real McCoy,
but we will have achieved our goal of making impossible tests possible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example: testing discount algorithm&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace the method figuring out what kind of discount the customer is eligible to with
  a hard-coded test double: e.g. -30% and validate the final price matches!&lt;/li&gt;
&lt;li&gt;In another scenario use a second test double which applies 10% discount when you
  submit a coupon code. Verify the final price matches expectations!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here we don't care how the actual discount percentage is determined. This is a
dependency. We want to test that the discount is actually applied properly, e.g.
there may be 2 or 3 different discounts and only 1 applies or no discount policy
for items that are already on sale. This is what you are testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; when the applying algorithm is tightly coupled with parts of the system
that select what types of discounts are available to the customer that means your code
needs refactoring since you will be not able to crate a test double (or it will be very hard
to do so).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;strong&gt;Fake Object&lt;/strong&gt; is a kind of Test Double that is similar to a Test Stub in many ways
including the need to install into the SUT a substitutable dependency but while a Test Stub
acts as a control point to inject indirect inputs into the SUT the Fake Object does not.
It merely provides a way for the interactions to occur in a self-consistent manner.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Variations (see &lt;a href="http://xunitpatterns.com/Fake%20Object.html"&gt;here&lt;/a&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fake database;&lt;/li&gt;
&lt;li&gt;In-memory database;&lt;/li&gt;
&lt;li&gt;Fake web service (or fake web server in the case of Django);&lt;/li&gt;
&lt;li&gt;Fake service layer;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Use of a &lt;strong&gt;Test Spy&lt;/strong&gt; is a simple and intuitive way to implement an observation point that
exposes the indirect outputs of the SUT so they can be verified.
Before we exercise the SUT, we install a Test Spy as a stand-in for depended-on component (DOC)
used by the SUT. The Test Spy is designed to act as an &lt;strong&gt;observation point&lt;/strong&gt; by recording the
method calls made to it by the SUT as it is exercised. During the result verification phase,
the test compares the actual values passed to the Test Spy by the SUT with the expected values.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; a test spy can be implemented via test double, exposing some of the functionality
to the test framework, e.g. expose internal log messages so we can validate them or can be
a very complex mock type of object.&lt;/p&gt;
&lt;p&gt;From &lt;em&gt;The Art of Unit Testing&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;em&gt;stub&lt;/em&gt; is a controllable replacement for an existing dependency (or collaborator)
 in the system. By using a stub, you can test your code without dealing with the dependency
 itself.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;mock object&lt;/em&gt; is a fake object in the system that decides whether the unit test
has passed or failed. It does so by verifying whether the object under test (e.g. a method)
interacted as expected with the fake object.&lt;/p&gt;
&lt;p&gt;Stubs can NEVER fail a test! The asserts are aways against the class/method under test.
Mocks can fail a test! We can assert how the class/method under test interacted with
the mock.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;When testing a registration form, which will send a confirmation email:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checking that invalid input is not accepted - will not trigger &lt;code&gt;send_mail()&lt;/code&gt; so
  we usually don't care about the dependency;&lt;/li&gt;
&lt;li&gt;Checking valid input will create a new account in the DB - we stub-out &lt;code&gt;send_mail()&lt;/code&gt;
  because we don't want to generate unnecessary email traffic to the outside world.&lt;/li&gt;
&lt;li&gt;Checking if a banned email address/domain can register - we mock &lt;code&gt;send_mail()&lt;/code&gt; so that
  we can assert that it was never called (together with other assertions that a correct
  error message was shown and no record was created in the database);&lt;/li&gt;
&lt;li&gt;Checking that valid, non-banned email address can register - we mock &lt;code&gt;send_mail()&lt;/code&gt; and
  later assert it was called with the actual address in question. This will verify that the
  system will attempt to deliver a confirmation email to the new user!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To summarize:
- &lt;strong&gt;When using mocks, stubs and fake objects we should be replacing external
    dependencies of the software under test, not internal methods from the SUT!&lt;/strong&gt;.
- Beware that many modern test framework use the singular term/class name Mock to
  refer to all of the things above. Depending on their behavior they can be true mocks
  or pure stubs.&lt;/p&gt;
&lt;p&gt;More practical examples with code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://atodorov.org/blog/2014/02/27/mocking-django-auth-profile-module-without-database/"&gt;Mocking Django AUTH_PROFILE_MODULE without a Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://atodorov.org/blog/2015/09/25/unit-testing-bad-stub-design-in-dnf/"&gt;Bad Stub Design in DNF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://atodorov.org/blog/2015/11/23/bad-stub-design-in-dnf/"&gt;Bad Stub Design in DNF, Pt.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://atodorov.org/blog/2016/03/31/beware-of-double-stubs-in-rspec/"&gt;Beware of Double Stubs in RSpec&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How do we test statistics where you have to create lots of records in different states to
make sure the aggregation algorithms work properly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Well there isn't much to do around this - create all the records and validate your queries!
Here the functionality is mostly filter records from the database, group and aggregate them
and display the results in table or chart form.&lt;/p&gt;
&lt;p&gt;Depending on the complexity of what is displayed I may even go without actually automating
this. If we have a representative set of test data (e.g. all possible states and values)
then just make sure the generated charts and tables show the expected information.&lt;/p&gt;
&lt;p&gt;In automation the only scenario I can think about is to re-implement the statistics
algorithm again! Doing a &lt;code&gt;select() &amp;amp;&amp;amp; stats()&lt;/code&gt; and &lt;code&gt;assert stats(test_data) == stats()&lt;/code&gt;
doesn't make a lot of sense becase we're using the result of one method to validate
itself! It will help discover problems with &lt;code&gt;select()&lt;/code&gt; but not with the actual
calculation!&lt;/p&gt;
&lt;p&gt;Once you reimplement every stats twice you will see why I tend to go for manual
testing here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to test various filters and searches which need lots of data?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First ask yourself the question - what do you need to test for?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;That all values from the webUI are passed down to the ORM&lt;/li&gt;
&lt;li&gt;That the ORM will actually return the records in question (e.g. active really means active
  not the opposite)&lt;/li&gt;
&lt;li&gt;which columns will be displayed (which is a UI thing)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For Kiwi TCMS search pages we don't do any kind of automated testing! These are
very static HTML forms that pass their values to a JavaScript function which passes
them to an API call and then renders the results! When you change it you have to validate it
manually but nothing more really.&lt;/p&gt;
&lt;p&gt;It is good to define test scenarios, especially based on customer bug reports but
essentially you are checking that a number of values are passed around which either
works or it doesn't. Not much logic and behavior to be tested there! &lt;strong&gt;Think like a tester, not
like a developer!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to test an API? Should we use an API spec schema and assert the server side
and client side based on it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is generally a good idea. The biggest troubles with APIs is that they change without
warning, sometimes in an incompatible way and clients are not aware of this. A few things you can do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use API versioning and leave older versions arround for as long as necessary.
  Facebook for example keeps their older API versions around for several years.&lt;/li&gt;
&lt;li&gt;Use some sort of contract testing/API specification to validate behavior.
  I find value here to have a test suite which explicitly exercises the external API in
  the desired ways (full coverage of what the application uses) so it can detect when
  something breaks. If this is not 100% all the time it will become useless very quickly.&lt;/li&gt;
&lt;li&gt;Record and replay may be useful at scale, Twitter uses similar approach with
  anonimizing the actual values being sent around and also accounting for parameter types,
  e.g. an int X can receive only ints and if someone tries to send a string that was
  probably an error. Twitter however has access to their entire production data and can
  perform such kind of sampling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What types of tests do QA people write?&lt;/strong&gt; (I split this from the next question).&lt;/p&gt;
&lt;p&gt;As should be evident by my many example nobody stops us from writing any kind of test
in any kind of programming language. This only depends on personal skills and the specifics of
the project we work on.&lt;/p&gt;
&lt;p&gt;Please refer back to the codec-rpm, lorax and welder-web projects. These are components
from a larger product named Composer which builds Linux cloud images.&lt;/p&gt;
&lt;p&gt;welder-web is
the front-end which integrates with Cockpit. This is written with React.js, includes some
component type tests (I think close to unit tests but I haven't worked on them), end-to-end
test suite (again JavaScript) similar to what you do with Selenium - fire up the browser
and click on widgets.&lt;/p&gt;
&lt;p&gt;lorax is a Python based backend with unit and integration tests in Python. I mostly work
on testing the resulting cloud images which uses a test framework for Bash script,
ansible, Docker and a bunch of vendor specific cli/api tools.&lt;/p&gt;
&lt;p&gt;codec-rpm is smaller component from another backend called BDCS which is written in Haskell.
As I showed you I've done some unit tests (and bug fixes even) and for bdcs-cli I did
work on similar cloud image tests in bash script. This component is now frozen but when/if
it picks up all the existing bash scripts will need to be ported plus any unit tests
which are missing will have to be reimplemented in Haskell. Whoever on the team is
free will get to do it.&lt;/p&gt;
&lt;p&gt;At the very beginning we used to have a 3rd backend written in Rust but that was abandoned
relatively quickly.&lt;/p&gt;
&lt;p&gt;To top this off a good QE person will often work on test related tooling to support their
team. I personally have worked on &lt;a href="https://github.com/sixty-north/cosmic-ray"&gt;Cosmic-Ray&lt;/a&gt; -
mutation testing tool for Python used by Amazon and others, I am the current maintainer of
&lt;a href="https://github.com/PyCQA/pylint-django"&gt;pylint-django&lt;/a&gt; - essentially a developer tool but
I like to stretch its usage with customized plugins and of course
&lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; which is a test management tool.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do they (testers) know what classes I am going to create so they are able to
write tests for them beforehand?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This comes from test driven development practices. In TDD (as everywhere in testing)
you will start with analisys what components are needed and how they will work.
Imagine that I want you to implement a class that represents a cash-desk which can
take money and store them, count them, etc. Imagine this is part of a banking application
where you can open accounts, transfer money between them, etc.&lt;/p&gt;
&lt;p&gt;With TDD I start by implementing tests for the desired behavior. I will &lt;code&gt;import solution&lt;/code&gt;
and I will create an object from the &lt;code&gt;Bill&lt;/code&gt; class to represent a 5 BGN note.
I don't care how you want to name your classes! The tests serve to enforce the interface
I need you to implement: module name, classes in the module, method names, behavior.&lt;/p&gt;
&lt;p&gt;Initially in TDD the tests will fail. Once functionality becomes to be implemented piece
by piece tests will start passing one by one! In TDD testers don't know, we expect developers
to do something otherwise tests fail and you can't merge!&lt;/p&gt;
&lt;p&gt;In practice there is a back-and-forth process!&lt;/p&gt;
&lt;p&gt;The above scenario is part of my training courses where I give students homework
assignments and I have already provided automated test suites for the classes and
modules they have to implement. Once the suite reports PASS I know the student
has at least done good enough implementation to meet the bare minimum of requirements.
See an example for the Cash-Desk and Bank-Account problems at
https://github.com/atodorov/qa-automation-python-selenium-101/tree/master/module04&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to test functionality which is date/time dependent?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For example a certain function should execute on week days but not on the weekend. How do we
test this? Very simple, we need to time travel, at least out tests do.&lt;/p&gt;
&lt;p&gt;Check-out &lt;a href="https://github.com/hnw/php-timecop"&gt;php-timecop&lt;/a&gt; and
&lt;a href="https://blog.trikoder.net/stub-php-date-and-the-crew-with-php-timecop-9a64a7d3b239"&gt;this introductory article&lt;/a&gt;.
Now that we know what stubs are we simply use a suitable library and stub out
date/time utilities. This essentially gives you the ability to freeze the
system clock or time travel backwards and forwards in time so you can execute
your tests in the appropriate environment. There are many such time-travel/time-freeze
libraries for all popular programming languages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Given the two variations of the method below:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="x"&gt;public function updateStatusPaid()&lt;/span&gt;
&lt;span class="x"&gt;{&lt;/span&gt;
&lt;span class="x"&gt;    $this-&amp;gt;update([&lt;/span&gt;
&lt;span class="x"&gt;        &amp;#39;date_paid&amp;#39; =&amp;gt; now(),&lt;/span&gt;
&lt;span class="x"&gt;        &amp;#39;status&amp;#39; =&amp;gt; &amp;#39;paid&amp;#39;&lt;/span&gt;
&lt;span class="x"&gt;    ]);&lt;/span&gt;
&lt;span class="x"&gt;}&lt;/span&gt;

&lt;span class="x"&gt;public function updateStatusPaid()&lt;/span&gt;
&lt;span class="x"&gt;{&lt;/span&gt;
&lt;span class="x"&gt;    $this-&amp;gt;date_paid = now();&lt;/span&gt;
&lt;span class="x"&gt;    $this-&amp;gt;status = &amp;#39;paid&amp;#39;;&lt;/span&gt;
&lt;span class="x"&gt;    $this-&amp;gt;save();&lt;/span&gt;
&lt;span class="x"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;How do we create a test which validates this method without touching the database?
Also we want to be able to switch between method implementations without updating the test code!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's examine this in details. Both of these methods change field values for the &lt;code&gt;$this&lt;/code&gt; object
and commit that to storage! There is no indication what happened inside other than the
object fields being changed in the underlying storage medium.&lt;/p&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;p&gt;1) Mock the &lt;code&gt;save()&lt;/code&gt; method or spy the entire storage layer. This will give you
   faster speed of execution but more importantly will let you examine the values before
   they leave the process memory space. Your best bet here is replacing the entire
   backend portion of the ORM layer which talks to the database. Drawback is that data may not be persistent
   between test executions/different test methods (depending on how they are executed and how
   the new storage layer works) so chained tests, which depend on data created by other tests
   or other parts of the system may break.
2) Modify your method to provide more information which can be consumed by the tests. This is
   called engineering for testability. The trouble with this method is that it doesn't
   expose anything to the outside world so the only way we can check that something has
   changed is to actually fetch it from storage and assert that it is different.
3) Test with the database included. The OP presumes touching a database during testing is
   a bad thing. As I've already pointed out this is not necessarily the case. Unless your data
   is so big that it is spread around cluster nodes in several shards using a database for
   testing is probably the easiest thing you can do.&lt;/p&gt;
&lt;p&gt;Now to the second part of the question: if your test is not tightly coupled with the method
implementation then it will not need to be changed once you change the implementation. That is
if you are asserting on independent system state then you should be fine.&lt;/p&gt;
&lt;h2&gt;Current problems&lt;/h2&gt;
&lt;p&gt;This is a list of problems we discussed, my views on them and similar items I've seen in the past.
They are valid across the board for many types of companies and teams and my only recommendation
here is to analyze the root of your problems and act to resolve them. IMO a lot of the times
the actual problems stem from not understanding the roots of what we are trying to validate,
not from technological limitations.&lt;/p&gt;
&lt;p&gt;Background:&lt;/p&gt;
&lt;p&gt;Company is delivering a digital product, over e-mail, without a required login procedure.
There are event ticket sites which work like this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem: email delivery fails, customer closes their browser and they can't get back to
what they paid for. Essentially customers locks themselves out of the product they
paid for.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is UX problem. Email is inherently unreliable and it can break at many steps along
the way. The product is not designed to be fault tolerant and to provide a way for the customer
to retrieve their digital products. Options include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browser cookies to remember orders in the last X days&lt;/li&gt;
&lt;li&gt;Well designed error/warning messages about possible data loss&lt;/li&gt;
&lt;li&gt;Require login (email or social) or other means of backup delivery (mobile phone,
  second email address, etc)&lt;/li&gt;
&lt;li&gt;Login is sometimes required by regulatory bodies (KYC practices) and is also a
  good starting point for additional marketting/relationship building activities&lt;/li&gt;
&lt;li&gt;Monitoring of email delivery providers and their operation. This is a business critical
  functionality so it must be treated like that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Product needs enough input data from customer to produce a deliverable.
&lt;strong&gt;Problem: Sometimes &lt;em&gt;enough&lt;/em&gt; may not be enough, that is the backend algorithm thinks it hass everything
and then it runs into some corner case from which it can't recover and is not able to
deliver its results to the customer.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I see this essentially as an UX problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ask customer for more info at the beginning - annoying, slows down initial
  product adoption, may break the conversion funnel;&lt;/li&gt;
&lt;li&gt;Calculate what we can and randomly pick options from DB (curated or based on statistics)
  and present them to customer;&lt;/li&gt;
&lt;li&gt;Previous point + allow the customer to proceed or go back and refine the selection
  which was automatically made for them - this is managing the UX experience around
  the technological limitations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Infrastructure problems: site doesn't open (not accessible for some reason), big email queue,
many levels of cache (using varnish)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Agressive monitoring of all of these items with alerts and combined charts. This is business
critical functionality and we need to always know what is the status of it. If you want to
be fancy couple this with an ML/AI algorithm which will predict failures in advance so you
can be on alert before that happens.&lt;/p&gt;
&lt;p&gt;More importantly each problem in production must be followed by a post-mortem session (more on that later).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration with payment processors: how do you test this in production ?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Again agressive monitoring when/if these integrations are up and running, then:&lt;/p&gt;
&lt;p&gt;Design a small test suite which goes directly on the website and examines if all payment options
are available. This will catch scenarios where you claim PayPal is supported but for some
reason the form didn't load. The problem may not be on your side! Check preferences per
country (may have been editted by admin on the backend), make sure what you promised is
always there.&lt;/p&gt;
&lt;p&gt;I've used similar approach in a trading startup. We run the suite once every hour directly
agains prod. Results were charted in Grafana together with the rest of the monitoring metrics.
In the first two days we found that the HTML form provided by the payment processor was changing
all the time - this was supposed to be stable. In the first week we discovered the payment
processor had issues on their own and were down for couple of hours during the night our time zone.&lt;/p&gt;
&lt;p&gt;There isn't much you can do when you rely on 3rd party services but you can either
- cache and retry later, masking the backend failures from the user at your own risk (payment may not be authorized later)
- do not accept payment or at least warn the customer if you are seeing/predicting 3rd party issues&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem: customers cancelling their payments after product was received&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes, in many countries you can do so many days after you paid and got access to something.
I have done so myself after non-delivery of items.&lt;/p&gt;
&lt;p&gt;In case this is deliberate action from the customer there isn't much you can do. In case it is
because they were frustrated due to problems overzealous monitoring and communicating back to
the customers will probably help.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Localization problems, missing translations, UI doesn't look good, missing images&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unless your test team speaks the language they can't understand shit. Best options IMO:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allow translator team to preview their work before it is comitted to the current version;
  A simple staging server will work for this. This is easy to integrate with any translation
  system;&lt;/li&gt;
&lt;li&gt;Use machine checks: missing format strings, unfilled data (e.g. missing translations),
  404 URLs. This is cheap to execute and can be done on Save and provide immediate feedback;&lt;/li&gt;
&lt;li&gt;Many systems provide the option to Review &amp;amp; Approve the work of another peer;&lt;/li&gt;
&lt;li&gt;Some visual testing tools (I don't have much experience here but I know they exist) which
  will detect strings that are too long and do not fit inside buttons and other widgets.
  This is more in the category of visual layout testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Problem: on mobile version, after new feature was added the 'Buy' button was overlayed
by another widget and was not visible&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This means that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;previously it was not defined what testing will be performed for the new feature;&lt;/li&gt;
&lt;li&gt;also that this 'Buy' button was not considered business critical functionality,
  which it is;&lt;/li&gt;
&lt;li&gt;the person who signed-off on this page was careless;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test management tools like &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; can help you with organizing
and documenting what needs to be tested. However, regardless of the system used, everything
starts with identifying which functionality is critical and must always be present! This is
the job of a tester!&lt;/p&gt;
&lt;p&gt;Once identified as critical you could probably use some tools for visual comparison to
make sure this button is always available on this (other) pages. Again a person must
identify all the possible interactions we want to check for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem: we released at 18:30 on Friday and went home. We discovered email delivery
was broken at 10:00 the next day&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Obviously this wasn't well tested since it broke. The root cause must be analized and
a test for it added.&lt;/p&gt;
&lt;p&gt;Also we are missing a minitoring metric here. If you are sending lots of emails then
a drop under, say 50K/hour probably means problems! What's the reason the existing monitoring
tools didn't trigger? Investigate and fix it.&lt;/p&gt;
&lt;p&gt;Last - do not &lt;em&gt;push &amp;amp; throw over the fence&lt;/em&gt;. This is the silo mentality of the past.
A small team can allow itself to make these mistakes just a few times, then comapny goes out of
business and the people who didn't care enough to resolve the problems go out of a job.&lt;/p&gt;
&lt;p&gt;Make a policy which gives you enough time to monitor production and revert in case of
problems. There are many reasons lots of companies don't release on Friday (while others do).
The point here is to put the policy and entire machinery in place so you can deal with
problems when they arise. If you are not equipped to deal with these problems
on late Friday night (or any other day/night of the week) you should not be making releases then.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem: how do we follow-up after a blunder?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In any growing team or company, especially a startup there is more demand to work on new
features than maintain existing code, resolve problems or work on non-visible items like
testing and monitoring which will help you the next time there are problems.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Swiss cheeze model framwork" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Swiss_cheese_model_of_accident_causation.png/330px-Swiss_cheese_model_of_accident_causation.png" /&gt;&lt;/p&gt;
&lt;p&gt;An evaluation framework like the
&lt;a href="https://en.wikipedia.org/wiki/Swiss_cheese_model"&gt;Swiss cheese model&lt;/a&gt; is a good place
to start. Prezi uses it extensively. Various sized holes are the different root causes which will lead to a problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;missing tests&lt;/li&gt;
&lt;li&gt;undocumented release procedure&lt;/li&gt;
&lt;li&gt;merged without code review&lt;/li&gt;
&lt;li&gt;incomplete feature specification&lt;/li&gt;
&lt;li&gt;too much work, task overload&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The cheese layers can be both technical and organizational. One of them can be
the business takeholders organization: wanting too much, not budgeting time for other
tasks, tight marketting schedule, etc.&lt;/p&gt;
&lt;p&gt;Once a post-mortem is held and the issues at hand analyzed you need to come up
with a plan of action. These are your JIRA tickets about what to do next.
Some will have immediate priority others will be important 1 year from now.
Once the action items are entered into your task tracking software the only thing left
to do is priritizing them accordingly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; tests, monitoring, even talking about a post-mortem and other seemingly
non-visible tasks are still important. If the business doesn't budget time for their
completion it will ultimately fail! You can not sustain adding new features quickly
for an extended period of time without taking the time to resolve your architecture,
infrastructure, team and who knows what other issues.&lt;/p&gt;
&lt;p&gt;Time and resources should be evaluated and assigned according to the importance of the task
and the various risks assiciated with it. This is no different from when we do
planning for new features. Consider having the ability to analyze, adapt and resolve
problems as the most important feature of your organization!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>How to authenticate Ansible with Azure</title><link href="http://atodorov.org/blog/2018/11/16/how-to-authenticate-ansible-with-azure/" rel="alternate"></link><updated>2018-11-16T09:30:00+02:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2018-11-16:blog/2018/11/16/how-to-authenticate-ansible-with-azure/</id><summary type="html">&lt;p&gt;As I am working on cloud image testing for
&lt;a href="http://weldr.io"&gt;Composer&lt;/a&gt; I need to create scripts that can provision
virtual machines in multiple cloud platforms. Instead of using their API directly
I can reuse the vast majority of
&lt;a href="https://docs.ansible.com/ansible/2.6/modules/list_of_cloud_modules.html"&gt;Ansible cloud modules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are modules for Azure of course however they poorly explain
how to configure authentication. Ansible docs say:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;For authentication with Azure you can pass parameters,
set environment variables or use a profile stored in
~/.azure/credentials. Authentication is possible using
a service principal or Active Directory user. To authenticate
via service principal, pass subscription_id, client_id, secret
and tenant or set environment variables AZURE_SUBSCRIPTION_ID,
AZURE_CLIENT_ID, AZURE_SECRET and AZURE_TENANT.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is how you go about configuring these variables.&lt;/p&gt;
&lt;p&gt;First install &lt;code&gt;azure-cli&lt;/code&gt; tools:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="c"&gt;# rpm --import https://packages.microsoft.com/keys/microsoft.asc&lt;/span&gt;
&lt;span class="c"&gt;# echo -e &amp;quot;[azure-cli]\nname=Azure CLI\nbaseurl=https://packages.microsoft.com/yumrepos/azure-cli\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc&amp;quot; &amp;gt; /etc/yum.repos.d/azure-cli.repo&lt;/span&gt;
&lt;span class="c"&gt;# yum install azure-cli&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;then login:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;az login
To sign in, use a web browser to open the page
https://microsoft.com/devicelogin and enter the code XXXXXXXXX to authenticate.
&lt;span class="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;cloudName&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;AzureCloud&amp;quot;&lt;/span&gt;,
    &lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;8d026bb1-.....&amp;quot;&lt;/span&gt;,
    &lt;span class="s2"&gt;&amp;quot;isDefault&amp;quot;&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;,
    &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;Pay-as-you-go&amp;quot;&lt;/span&gt;,
    &lt;span class="s2"&gt;&amp;quot;state&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;Enabled&amp;quot;&lt;/span&gt;,
    &lt;span class="s2"&gt;&amp;quot;tenantId&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;9f340302-......&amp;quot;&lt;/span&gt;,
    &lt;span class="s2"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;atodorov@....&amp;quot;&lt;/span&gt;,
      &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here &lt;code&gt;id==AZURE_SUBSCRITION_ID&lt;/code&gt; and &lt;code&gt;tenantId==AZURE_TENANT&lt;/code&gt;! Next you need
client id and secret before Ansible can be able to authenticate with Azure!
In fact you need to register an Active Directory Service Principal
which will authenticate with the Azure REST API, in other words when
executing Ansible commands in your shell (or via test script) that will be
treated as an application which must be allowed access to Azure resources.&lt;/p&gt;
&lt;p&gt;From the command line this is done by:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;az ad sp create-for-rbac --name http://ansible-atodorov --role owner --scopes &lt;span class="s2"&gt;&amp;quot;/subscriptions/&lt;/span&gt;&lt;span class="nv"&gt;$AZURE_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="s2"&gt;/resourceGroups/&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;&amp;quot;appId&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;f86af23a-......&amp;quot;&lt;/span&gt;,
  &lt;span class="s2"&gt;&amp;quot;displayName&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;ansible-atodorov&amp;quot;&lt;/span&gt;,
  &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;http://ansible-atodorov&amp;quot;&lt;/span&gt;,
  &lt;span class="s2"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;37d908aa-.......&amp;quot;&lt;/span&gt;,
  &lt;span class="s2"&gt;&amp;quot;tenant&amp;quot;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;quot;9f340302-.........&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note: resource group is an Azure term, you can find more about it
&lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this example &lt;code&gt;appId==AZURE_CLIENT_ID&lt;/code&gt; and &lt;code&gt;password==AZURE_SECRET&lt;/code&gt;. After exporting
these environment variables you should be able to use Ansible to upload blobs to
Azure or start virtual machines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8d026bb1-.....
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_TENANT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9f340302-..............
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;f86af23a-...........
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;37d908aa-..............

&lt;span class="nv"&gt;$ &lt;/span&gt;ansible localhost -m azure_rm_storageblob -a &lt;span class="s2"&gt;&amp;quot;resource_group=composer storage_account_name=composerredhat container=composerredhat blob=linux.vhd src=linux.vhd blob_type=page&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category></entry><entry><title>Introducing pylint-django 2.0</title><link href="http://atodorov.org/blog/2018/07/24/introducing-pylint-django-20/" rel="alternate"></link><updated>2018-07-24T09:50:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2018-07-24:blog/2018/07/24/introducing-pylint-django-20/</id><summary type="html">&lt;p&gt;Today I have released pylint-django version 2.0 on PyPI.
The changes are centered around compatibility with the latest pylint 2.0 and
astroid 2.0 versions. I've also bumped pylint-django's version number to reflact
that.&lt;/p&gt;
&lt;p&gt;A major component, class transformations, was updated so don't be surprised if
there are bugs. All the existing test cases pass but you never know what sort
of edge case there could be.&lt;/p&gt;
&lt;p&gt;I'm also hosting a workshop/corporate training about writing pylint plugins.
If you are interested see this &lt;a href="http://MrSenko.com/pylint-workshop/"&gt;page&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category><category term="Python"></category><category term="Django"></category></entry><entry><title>Upstream rebuilds with Jenkins Job Builder</title><link href="http://atodorov.org/blog/2018/07/06/upstream-rebuilds-with-jenkins-job-builder/" rel="alternate"></link><updated>2018-07-06T13:20:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2018-07-06:blog/2018/07/06/upstream-rebuilds-with-jenkins-job-builder/</id><summary type="html">&lt;p&gt;I have been working on &lt;a href="http://weldr.io/"&gt;Weldr&lt;/a&gt; for some time now.
It is a multi-component software with several layers built on top of
each other as seen on the image below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Weldr components" src="/images/welder_upstream.png" /&gt;&lt;/p&gt;
&lt;p&gt;One of the risks that we face is introducing changes in
downstream components which are going to break something up the stack!
In this post I am going to show you how I have configured
Jenkins to trigger dependent rebuilds and report all of the statuses
back to the original GitHub PR. All of the code below is Jenkins Job Builder
yaml.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bdcs&lt;/code&gt; is the first layer of our software stack. It provides command line
utilities. &lt;code&gt;codec-rpm&lt;/code&gt; is a library component that facilitates working
with RPM packages (in Haskell). &lt;code&gt;bdcs&lt;/code&gt; links to &lt;code&gt;codec-rpm&lt;/code&gt; when it is compiled,
&lt;code&gt;bdcs&lt;/code&gt; uses some functions and data types from &lt;code&gt;codec-rpm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When a pull request is opened against &lt;code&gt;codec-rpm&lt;/code&gt; and testing completes successfully
I want to reuse that particular version of the &lt;code&gt;codec-rpm&lt;/code&gt; library and
rebuild/test &lt;code&gt;bdcs&lt;/code&gt; with that.&lt;/p&gt;
&lt;h2&gt;YAML configuration&lt;/h2&gt;
&lt;p&gt;All jobs have the following structure: -trigger -&amp;gt; -provision -&amp;gt; -runtest -&amp;gt; -teardown.
This means that Jenkins will start executing a new job when it gets triggered by
an event in GitHub (commit to master branch or new pull request), then it will
provision a slave VM in OpenStack, execute the test suite on the slave and destroy
all of the resources at the end. This is repeated twice: for master branch and for
pull requests! Here's how the -runtest jobs look:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;job-template&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-provision&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;node&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;master&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;parameters&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;string&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;PROVIDER&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;scm&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
        &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;git&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;url&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://github.com/weldr/{repo_name}.git&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;refspec&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;${{git_refspec}}&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;branches&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
              &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;${{git_branch}}&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;builders&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;github-notifier&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;shell&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="p-Indicator"&gt;|&lt;/span&gt;
            &lt;span class="no"&gt;#!/bin/bash -ex&lt;/span&gt;
            &lt;span class="no"&gt;# do the openstack provisioning here&lt;/span&gt;
        &lt;span class="c1"&gt;# NB: runtest_job is passed to us via the -trigger job&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;trigger-builds&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;project&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;${{runtest_job}}&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;block&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;current-parameters&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;condition&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;SUCCESS&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;fail-on-missing&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;


&lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;job-template&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-master-runtest&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;node&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;cinch-slave&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;project-type&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;freestyle&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;description&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;master&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{name}!&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;scm&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
        &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;git&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;url&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://github.com/weldr/{repo_name}.git&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;branches&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
                &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;master&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;builders&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;github-notifier&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;conditional-step&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;condition-kind&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;regex-match&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;regex&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;^.+$&amp;quot;&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;label&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;${{UPSTREAM_BUILD}}&amp;#39;&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;on-evaluation-failure&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;dont-run&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;steps&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
            &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;copyartifact&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
                &lt;span class="l-Scalar-Plain"&gt;project&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;${{UPSTREAM_BUILD}}&lt;/span&gt;
                &lt;span class="l-Scalar-Plain"&gt;which-build&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;specific-build&lt;/span&gt;
                &lt;span class="l-Scalar-Plain"&gt;build-number&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;${{UPSTREAM_BUILD_NUMBER}}&lt;/span&gt;
                &lt;span class="l-Scalar-Plain"&gt;filter&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;${{UPSTREAM_ARTIFACT}}&lt;/span&gt;
                &lt;span class="l-Scalar-Plain"&gt;flatten&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;shell&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="p-Indicator"&gt;|&lt;/span&gt;
            &lt;span class="no"&gt;#!/bin/bash -ex&lt;/span&gt;
            &lt;span class="no"&gt;make ci&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;publishers&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;trigger-parameterized-builds&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;project&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-teardown&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;current-parameters&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;github-notifier&lt;/span&gt;


&lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;job-template&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-PR-runtest&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;node&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;cinch-slave&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;description&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PRs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{name}!&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;scm&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
        &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;git&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;url&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://github.com/weldr/{repo_name}.git&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;refspec&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;+refs/pull/*:refs/remotes/origin/pr/*&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;branches&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# builds the commit hash instead of a branch&lt;/span&gt;
                &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;${{ghprbActualCommit}}&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;builders&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;github-notifier&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;shell&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="p-Indicator"&gt;|&lt;/span&gt;
            &lt;span class="no"&gt;#!/bin/bash -ex&lt;/span&gt;
            &lt;span class="no"&gt;make ci&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;conditional-step&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;condition-kind&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;current-status&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;condition-worst&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;SUCCESS&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;condition-best&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;SUCCESS&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;on-evaluation-failure&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;dont-run&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;steps&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
            &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;shell&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="p-Indicator"&gt;|&lt;/span&gt;
                &lt;span class="no"&gt;#!/bin/bash -ex&lt;/span&gt;
                &lt;span class="no"&gt;make after_success&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;publishers&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;archive&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;artifacts&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{artifacts_path}&amp;#39;&lt;/span&gt;
          &lt;span class="l-Scalar-Plain"&gt;allow-empty&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{artifacts_empty}&amp;#39;&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;conditional-publisher&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;condition-kind&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{execute_dependent_job}&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;on-evaluation-failure&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;dont-run&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;action&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
              &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;trigger-parameterized-builds&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
                &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;project&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{dependent_job}&amp;#39;&lt;/span&gt;
                  &lt;span class="l-Scalar-Plain"&gt;current-parameters&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
                  &lt;span class="l-Scalar-Plain"&gt;predefined-parameters&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="p-Indicator"&gt;|&lt;/span&gt;
                    &lt;span class="no"&gt;UPSTREAM_ARTIFACT={artifacts_path}&lt;/span&gt;
                    &lt;span class="no"&gt;UPSTREAM_BUILD=${{JOB_NAME}}&lt;/span&gt;
                    &lt;span class="no"&gt;UPSTREAM_BUILD_NUMBER=${{build_number}}&lt;/span&gt;
                  &lt;span class="l-Scalar-Plain"&gt;condition&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;SUCCESS&amp;#39;&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;trigger-parameterized-builds&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;project&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-teardown&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;current-parameters&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;github-notifier&lt;/span&gt;


&lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;job-group&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-tests&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;jobs&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-provision&amp;#39;&lt;/span&gt;
    &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-teardown&amp;#39;&lt;/span&gt;
    &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-master-trigger&amp;#39;&lt;/span&gt;
    &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-master-runtest&amp;#39;&lt;/span&gt;
    &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-PR-trigger&amp;#39;&lt;/span&gt;
    &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-PR-runtest&amp;#39;&lt;/span&gt;


&lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;job&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;codec-rpm-rebuild-bdcs&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;node&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;master&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;project-type&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;freestyle&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;description&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Rebuild&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bdcs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;codec-rpm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PR!&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;scm&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
        &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;git&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;url&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://github.com/weldr/codec-rpm.git&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;refspec&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;+refs/pull/*:refs/remotes/origin/pr/*&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;branches&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# builds the commit hash instead of a branch&lt;/span&gt;
                &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;${ghprbActualCommit}&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;builders&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;github-notifier&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;trigger-builds&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
          &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;project&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;bdcs-master-trigger&amp;#39;&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;block&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;
            &lt;span class="l-Scalar-Plain"&gt;predefined-parameters&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="p-Indicator"&gt;|&lt;/span&gt;
                &lt;span class="no"&gt;UPSTREAM_ARTIFACT=${UPSTREAM_ARTIFACT}&lt;/span&gt;
                &lt;span class="no"&gt;UPSTREAM_BUILD=${UPSTREAM_BUILD}&lt;/span&gt;
                &lt;span class="no"&gt;UPSTREAM_BUILD_NUMBER=${UPSTREAM_BUILD_NUMBER}&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;publishers&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;github-notifier&lt;/span&gt;


&lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;project&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;name&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;codec-rpm&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;dependent_job&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-rebuild-bdcs&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;execute_dependent_job&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;always&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;artifacts_path&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;dist/{name}-latest.tar.gz&amp;#39;&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;artifacts_empty&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt; &lt;span class="l-Scalar-Plain"&gt;false&lt;/span&gt;
    &lt;span class="l-Scalar-Plain"&gt;jobs&lt;/span&gt;&lt;span class="p-Indicator"&gt;:&lt;/span&gt;
      &lt;span class="p-Indicator"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;{name}-tests&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Publishing artifacts&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;make after_success&lt;/code&gt; is responsible for creating a tarball if &lt;code&gt;codec-rpm&lt;/code&gt; test suite
passed. This tarball gets uploaded as artifact into Jenkins and we can make use of it later!&lt;/p&gt;
&lt;p&gt;Inside -master-runtest I have a &lt;code&gt;conditional-step&lt;/code&gt; inside the &lt;code&gt;builders&lt;/code&gt; section which
will copy the artifacts from the previous build if they are present. Notice that I copy
artifacts for a particular job number, which is the job for codec-rpm PR.&lt;/p&gt;
&lt;p&gt;Making use of local artifacts is handled inside bdcs' &lt;code&gt;make ci&lt;/code&gt; because it is
per-project specific and because I'd like to reuse my YAML templates.&lt;/p&gt;
&lt;h2&gt;Reporting statuses to GitHub&lt;/h2&gt;
&lt;p&gt;For &lt;code&gt;github-notifier&lt;/code&gt; to be able to report statuses back to the pull request
the job needs to be configured with the git repository this pull request came from.
This is done by specifying the same &lt;code&gt;scm&lt;/code&gt; section for all jobs that are related and
&lt;code&gt;current-parameters: true&lt;/code&gt; to pass the revision information to the other jobs.&lt;/p&gt;
&lt;p&gt;This also means that if I want to report status from &lt;code&gt;codec-rpm-rebuild-bdcs&lt;/code&gt; then
it needs to be configured for the &lt;code&gt;codec-rpm&lt;/code&gt; repository (see yaml) but somehow
it should trigger jobs for another repository!&lt;/p&gt;
&lt;p&gt;When jobs are started via &lt;code&gt;trigger-parameterized-builds&lt;/code&gt; their statuses are reported
separately to GitHub. When they are started via &lt;code&gt;trigger-builds&lt;/code&gt; there should be only
one status reported.&lt;/p&gt;
&lt;h2&gt;Trigger chain for dependency rebuilds&lt;/h2&gt;
&lt;p&gt;With all of the above info we can now look at the &lt;code&gt;codec-rpm-rebuild-bdcs&lt;/code&gt; job.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is configured for the codec-rpm repository so it will report its status to the PR&lt;/li&gt;
&lt;li&gt;It is conditionally started after &lt;code&gt;codec-rpm-PR-runtest&lt;/code&gt; finishes successfully&lt;/li&gt;
&lt;li&gt;It triggers &lt;code&gt;bdcs-master-trigger&lt;/code&gt; which in turn will rebuild &amp;amp; retest the bdcs component.
  Additional parameters specify whether we're going to use locally built artifacts or
  attempt to download then from Hackage&lt;/li&gt;
&lt;li&gt;It uses &lt;code&gt;block: true&lt;/code&gt; so that the status of &lt;code&gt;codec-rpm-rebuild-bdcs&lt;/code&gt; is dependent
  on the status of &lt;code&gt;bdcs-master-runtest&lt;/code&gt; (everything in the job chain uses &lt;code&gt;block: true&lt;/code&gt; because of this)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How this looks like in practice&lt;/h2&gt;
&lt;p&gt;I have opened &lt;a href="https://github.com/weldr/codec-rpm/pull/39"&gt;codec-rpm #39&lt;/a&gt;
to validate my configuration. The chain of jobs that gets executed in Jenkins is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gd"&gt;--- console.log for bdcs-master-runtest ---&lt;/span&gt;
Started by upstream project &amp;quot;bdcs-jslave-1-provision&amp;quot; build number 267
originally caused by:
 Started by upstream project &amp;quot;bdcs-master-trigger&amp;quot; build number 133
 originally caused by:
  Started by upstream project &amp;quot;codec-rpm-rebuild-bdcs&amp;quot; build number 25
  originally caused by:
   Started by upstream project &amp;quot;codec-rpm-PR-runtest&amp;quot; build number 77
   originally caused by:
    Started by upstream project &amp;quot;codec-rpm-jslave-1-provision&amp;quot; build number 178
    originally caused by:
     Started by upstream project &amp;quot;codec-rpm-PR-trigger&amp;quot; build number 118
     originally caused by:
      GitHub pull request #39 of commit b00c923065e367afd5b7a7cc068b049bb1ed25e1, no merge conflicts.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Statuses are reported on GitHub as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt="example of PR statuses" src="/images/codec-rpm-pr-39.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;default&lt;/code&gt; is coming from the provisioning step and I think this is some sort of a bug
or misconfiguration of the provisioning job. We don't really care about this.&lt;/p&gt;
&lt;p&gt;On the picture you can see that &lt;code&gt;codec-rpm-PR-runtest&lt;/code&gt; was successful but
&lt;code&gt;codec-rpm-rebuild-bdcs&lt;/code&gt; was not. The actual error when compiling bdcs is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;BDCS&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;RPM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nl"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Couldn&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;C8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ByteString&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
      &lt;span class="n"&gt;Expected&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conduit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.2.13.1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conduit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conduit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConduitM&lt;/span&gt;
                       &lt;span class="n"&gt;C8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ByteString&lt;/span&gt;
                       &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Void&lt;/span&gt;
                       &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CsMonad&lt;/span&gt;
                       &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Maybe&lt;/span&gt; &lt;span class="n"&gt;ObjectDigest&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;Actual&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conduit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.2.13.1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conduit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conduit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConduitM&lt;/span&gt;
                       &lt;span class="n"&gt;Entry&lt;/span&gt;
                       &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Void&lt;/span&gt;
                       &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CsMonad&lt;/span&gt;
                       &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Maybe&lt;/span&gt; &lt;span class="n"&gt;ObjectDigest&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namely&lt;/span&gt;
        &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;getZipConduit&lt;/span&gt;
           &lt;span class="p"&gt;((,)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ZipConduit&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ZipConduit&lt;/span&gt; &lt;span class="n"&gt;digests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
      &lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namely&lt;/span&gt;
        &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="n"&gt;getZipConduit&lt;/span&gt;
               &lt;span class="p"&gt;((,)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ZipConduit&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ZipConduit&lt;/span&gt; &lt;span class="n"&gt;digests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
      &lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namely&lt;/span&gt;
        &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;runConduit&lt;/span&gt;
           &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
                 &lt;span class="n"&gt;getZipConduit&lt;/span&gt;
                   &lt;span class="p"&gt;((,)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ZipConduit&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ZipConduit&lt;/span&gt; &lt;span class="n"&gt;digests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;                     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;getZipConduit&lt;/span&gt; &lt;span class="p"&gt;((,)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ZipConduit&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt;                        &lt;span class="o"&gt;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That is because PR #39 changes the return type of &lt;code&gt;Codec.RPM.Conduit::payloadContentsC&lt;/code&gt;
from &lt;code&gt;Entry&lt;/code&gt; to &lt;code&gt;C8.ByteString&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;social image CC by https://pxhere.com/en/photo/226978&lt;/em&gt;&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>Introducing pylint-django 0.8.0</title><link href="http://atodorov.org/blog/2018/01/22/introducing-pylint-django-080/" rel="alternate"></link><updated>2018-01-22T17:00:00+02:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2018-01-22:blog/2018/01/22/introducing-pylint-django-080/</id><summary type="html">&lt;p&gt;Since my previous post was about
&lt;a href="http://atodorov.org/blog/2018/01/05/how-to-write-pylint-checker-plugins/"&gt;writing pylint plugins&lt;/a&gt;
I figured I'd let you know that I've released
&lt;a href="https://github.com/landscapeio/pylint-django"&gt;pylint-django&lt;/a&gt; version 0.8.0
over the weekend. This release merges all pull requests which were
pending till now so make sure to read the change log.&lt;/p&gt;
&lt;p&gt;Starting with this release Colin Howe and myself are the new
maintainers of this package. My immediate goal is to triage all of the
open issue and figure out if they still reproduce. If yes try to
come up with fixes for them or at least get the conversation going again.&lt;/p&gt;
&lt;p&gt;My next goal is to integrate pylint-django with
&lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; and start resolving all the 4000+
errors and warnings that it produces.&lt;/p&gt;
&lt;p&gt;You are welcome to contribute of course. I'm also interested in hosting a
workshop on the topic of pylint plugins.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category><category term="Python"></category><category term="Django"></category></entry><entry><title>How to write pylint checker plugins</title><link href="http://atodorov.org/blog/2018/01/05/how-to-write-pylint-checker-plugins/" rel="alternate"></link><updated>2018-01-05T13:00:00+02:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2018-01-05:blog/2018/01/05/how-to-write-pylint-checker-plugins/</id><summary type="html">&lt;p&gt;In this post I will walk you through the process of learning how to write
additional checkers for pylint!&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Read
   &lt;a href="https://pylint.readthedocs.io/en/latest/development_guide/contribute.html"&gt;Contributing to pylint&lt;/a&gt;
   to get basic knowledge of how to execute the test suite and how it is structured.
   Basically call &lt;code&gt;tox -e py36&lt;/code&gt;. Verify that all tests &lt;strong&gt;PASS&lt;/strong&gt; locally!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read pylint's
   &lt;a href="https://pylint.readthedocs.io/en/latest/how_tos/index.html"&gt;How To Guides&lt;/a&gt;,
   in particular the section about writing a new checker. A plugin is usually
   a Python module that registers a new checker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most of pylint checkers are AST based, meaning they operate on the
   abstract syntax tree of the source code. You will have to familiarize
   yourself with the AST node reference for the &lt;code&gt;astroid&lt;/code&gt; and &lt;code&gt;ast&lt;/code&gt; modules.
   Pylint uses Astroid for parsing and augmenting the AST.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; there is compact and excellent documentation provided by the
   &lt;em&gt;Green Tree Snakes&lt;/em&gt; project. I would recommend the
   &lt;a href="http://greentreesnakes.readthedocs.io/en/latest/nodes.html"&gt;Meet the Nodes&lt;/a&gt;
   chapter.&lt;/p&gt;
&lt;p&gt;Astroid also provides exhaustive documentation and
   &lt;a href="http://astroid.readthedocs.io/en/latest/api/astroid.nodes.html"&gt;node API reference&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; sometimes Astroid node class names don't match the ones from ast!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your interactive shell weapons are &lt;code&gt;ast.dump()&lt;/code&gt;, &lt;code&gt;ast.parse()&lt;/code&gt;, &lt;code&gt;astroid.parse()&lt;/code&gt; and
   &lt;code&gt;astroid.extract_node()&lt;/code&gt;. I use them inside an interactive Python shell to
   figure out how a piece of source code is parsed and converted back to AST nodes!
   You can also try this
   &lt;a href="https://bitbucket.org/takluyver/greentreesnakes/src/default/astpp.py?fileviewer=file-view-default"&gt;ast node pretty printer&lt;/a&gt;!
   I personally haven't used it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How pylint processes the AST tree&lt;/h2&gt;
&lt;p&gt;Every checker class may include special methods with names
&lt;code&gt;visit_xxx(self, node)&lt;/code&gt; and &lt;code&gt;leave_xxx(self, node)&lt;/code&gt; where xxx is the lowercase
name of the node class (as defined by astroid). These methods are executed
automatically when the parser iterates over nodes of the respective type.&lt;/p&gt;
&lt;p&gt;All of the magic happens inside such methods. They are responsible for collecting
information about the context of specific statements or patterns that you wish to
detect. The hard part is figuring out how to collect all the information you need
because sometimes it can be spread across nodes of several different types (e.g.
more complex code patterns).&lt;/p&gt;
&lt;p&gt;There is a special decorator called &lt;code&gt;@utils.check_messages&lt;/code&gt;. You have to list
all message ids that your &lt;code&gt;visit_&lt;/code&gt; or &lt;code&gt;leave_&lt;/code&gt; method will generate!&lt;/p&gt;
&lt;h2&gt;How to select message codes and IDs&lt;/h2&gt;
&lt;p&gt;One of the most unclear things for me is message codes. pylint
&lt;a href="https://pylint.readthedocs.io/en/latest/how_tos/custom_checkers.html"&gt;docs&lt;/a&gt; say&lt;/p&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The message-id should be a 5-digit number, prefixed with a message category.
There are multiple message categories, these being &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;W&lt;/code&gt;, &lt;code&gt;E&lt;/code&gt;, &lt;code&gt;F&lt;/code&gt;, &lt;code&gt;R&lt;/code&gt;,
standing for &lt;code&gt;Convention&lt;/code&gt;, &lt;code&gt;Warning&lt;/code&gt;, &lt;code&gt;Error&lt;/code&gt;, &lt;code&gt;Fatal&lt;/code&gt; and &lt;code&gt;Refactoring&lt;/code&gt;.
The rest of the 5 digits should not conflict with existing checkers and they
should be consistent across the checker. For instance, the first two digits should
not be different across the checker.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm usually having troubles with the numbering part so you will have to get creative
or look at existing checker codes.&lt;/p&gt;
&lt;h2&gt;Practical example&lt;/h2&gt;
&lt;p&gt;In &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; there's legacy code that looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_cases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;case_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;trs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestRun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id__in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pre_process_ids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_ids&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;tcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case_id__in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pre_process_ids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case_ids&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tcs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_case_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice the dangling &lt;code&gt;return&lt;/code&gt; statement at the end! It is useless because when missing
the default return value of this function will still be &lt;code&gt;None&lt;/code&gt;. So I've decided to
create a plugin for that.&lt;/p&gt;
&lt;p&gt;Armed with the knowledge above I first try the ast parser in the console:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="mf"&gt;3.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Oct&lt;/span&gt;  &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GCC&lt;/span&gt; &lt;span class="mf"&gt;4.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="mi"&gt;20150623&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Red&lt;/span&gt; &lt;span class="n"&gt;Hat&lt;/span&gt; &lt;span class="mf"&gt;4.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt;
&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;license&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;information&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ast&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;astroid&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;def func():&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    return&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;Module(body=[FunctionDef(name=&amp;#39;func&amp;#39;, args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Return(value=None)], decorator_list=[], returns=None)])&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;astroid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;def func():&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    return&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0x7f5b04621b38&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionDef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0x7f5b046219e8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionDef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0x7f5b046219e8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Return&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0x7f5b04621c18&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As you can see there is a &lt;code&gt;FunctionDef&lt;/code&gt; node representing the function and it has
a &lt;code&gt;body&lt;/code&gt; attribute which is a list of all statements inside the function. The last
element is &lt;code&gt;.body[-1]&lt;/code&gt; and it is of type &lt;code&gt;Return&lt;/code&gt;! The &lt;code&gt;Return&lt;/code&gt; node also has an
attribute called &lt;code&gt;.value&lt;/code&gt; which is the return value! The complete code will look
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;span class="filename"&gt;uselessreturn.py&lt;/span&gt;&lt;pre&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;astroid&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pylint&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;checkers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pylint&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;interfaces&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pylint.checkers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UselessReturnChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseChecker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__implements__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interfaces&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IAstroidChecker&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;useless-return&amp;#39;&lt;/span&gt;

    &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;&amp;#39;R2119&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Useless return at end of function or method&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="s"&gt;&amp;#39;useless-return&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="s"&gt;&amp;#39;Emitted when a bare return statement is found at the end of &amp;#39;&lt;/span&gt;
                  &lt;span class="s"&gt;&amp;#39;function or method definition&amp;#39;&lt;/span&gt;
                  &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="nd"&gt;@utils.check_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;useless-return&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visit_functiondef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;            Checks for presence of return statement at the end of a function&lt;/span&gt;
&lt;span class="sd"&gt;            &amp;quot;return&amp;quot; or &amp;quot;return None&amp;quot; are useless because None is the default&lt;/span&gt;
&lt;span class="sd"&gt;            return type if they are missing&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="c"&gt;# if the function has empty body then return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;astroid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c"&gt;# e.g. &amp;quot;return&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;useless-return&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c"&gt;# e.g. &amp;quot;return None&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;astroid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;useless-return&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;required method to auto register this checker&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;linter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UselessReturnChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here's how to execute the new plugin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./myplugins pylint --load-plugins&lt;span class="o"&gt;=&lt;/span&gt;uselessreturn tcms/xmlrpc/api/testrun.py &lt;span class="p"&gt;|&lt;/span&gt; grep useless-return
W: 40, 0: Useless &lt;span class="k"&gt;return&lt;/span&gt; at end of &lt;span class="k"&gt;function&lt;/span&gt; or method &lt;span class="o"&gt;(&lt;/span&gt;useless-return&lt;span class="o"&gt;)&lt;/span&gt;
W:117, 0: Useless &lt;span class="k"&gt;return&lt;/span&gt; at end of &lt;span class="k"&gt;function&lt;/span&gt; or method &lt;span class="o"&gt;(&lt;/span&gt;useless-return&lt;span class="o"&gt;)&lt;/span&gt;
W:242, 0: Useless &lt;span class="k"&gt;return&lt;/span&gt; at end of &lt;span class="k"&gt;function&lt;/span&gt; or method &lt;span class="o"&gt;(&lt;/span&gt;useless-return&lt;span class="o"&gt;)&lt;/span&gt;
W:495, 0: Useless &lt;span class="k"&gt;return&lt;/span&gt; at end of &lt;span class="k"&gt;function&lt;/span&gt; or method &lt;span class="o"&gt;(&lt;/span&gt;useless-return&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;NOTES:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you contribute this code upstream and pylint releases it you will get a traceback:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;pylint.exceptions.InvalidMessageError: Message symbol &amp;#39;useless-return&amp;#39; is already defined
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;this means your checker has been released in the latest version and you can drop the custom
plugin!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is example is fairly simple because the AST tree provides the information we
  need in a very handy way. Take a look at some of
  &lt;a href="https://github.com/PyCQA/pylint/pulls/atodorov"&gt;my other checkers&lt;/a&gt; to get a feeling
  of what a more complex checker looks like!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write and run tests for your new checkers, especially if contributing upstream.
  Have in mind that the new checker will be executed against existing code and in
  combination with other checkers which could lead to some interesting results.
  I will leave the testing to yourself, all is written in the documentation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This particular example I've contributed as
&lt;a href="https://github.com/PyCQA/pylint/pull/1821"&gt;PR #1821&lt;/a&gt; which happened to contradict
an existing checker. The update, raising warnings only when there's a single return
statement in the function body, is &lt;a href="https://github.com/PyCQA/pylint/pull/1823"&gt;PR #1823&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Workshop around the corner&lt;/h2&gt;
&lt;p&gt;I will be working together with &lt;a href="http://hacksoft.io"&gt;HackSoft&lt;/a&gt; on an in-house
workshop/training for writing pylint plugins. I'm also looking at reviving
&lt;a href="https://github.com/landscapeio/pylint-django/"&gt;pylint-django&lt;/a&gt; so we can
write more plugins specifically for Django based projects.&lt;/p&gt;
&lt;p&gt;If you are interested in workshop and training on the topic let me know!&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category><category term="Python"></category><category term="Django"></category></entry><entry><title>On Pytest-django and LiveServerTestCase with initial data</title><link href="http://atodorov.org/blog/2017/12/26/on-pytest-django-and-liveservertestcase-with-initial-data/" rel="alternate"></link><updated>2017-12-26T11:20:00+02:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-12-26:blog/2017/12/26/on-pytest-django-and-liveservertestcase-with-initial-data/</id><summary type="html">&lt;p&gt;While working on &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; I've had the opportunity to
learn in-depth about how the standard test case classes in Django work. This
is a quick post about creating initial data and order of execution!&lt;/p&gt;
&lt;h2&gt;Initial test data for TransactionTestCase or LiveServerTestCase&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;class LiveServerTestCase(TransactionTestCase)&lt;/code&gt;, as the name suggests, provides a running
Django instance during testing. We use that for Kiwi's XML-RPC API tests, issuing
http requests against the live server instance and examining the responses!
For testing to work we also need some initial data. There are few key items
that need to be taken into account to accomplish that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;self._fixture_teardown()&lt;/code&gt; - performs &lt;code&gt;./manage.py flush&lt;/code&gt; which
  deletes all records from the database, including the ones created during initial
  migrations;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;self.serialized_rollback&lt;/code&gt; - when set to True will serialize initial
  records from the database into a string and then load this back. Required if
  subsequent tests need to have access to the records created during migrations!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cls.setUpTestData&lt;/code&gt; is an attribute of &lt;code&gt;class TestCase(TransactionTestCase)&lt;/code&gt; and hence
  can't be used to create records before any transaction based test case is executed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;self._fixture_setup()&lt;/code&gt; is where the serialized rollback happens, thus it can
  be used to create initial data for your tests!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Kiwi TCMS all XML-RPC test classes have &lt;code&gt;serialized_rollback = True&lt;/code&gt; and
implement a &lt;code&gt;_fixture_setup()&lt;/code&gt; method instead of &lt;code&gt;setUpTestData()&lt;/code&gt; to create the
necessary records before testing!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; you can also use fixtures in the above scenario but I don't like using them
and we've deleted all fixtures from Kiwi TCMS a long time ago so I didn't feel like
going back to that!&lt;/p&gt;
&lt;h2&gt;Order of test execution&lt;/h2&gt;
&lt;p&gt;From
&lt;a href="https://docs.djangoproject.com/en/2.0/topics/testing/overview/#order-in-which-tests-are-executed"&gt;Django's docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In order to guarantee that all TestCase code starts with a clean database, the Django test runner reorders
tests in the following way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All TestCase subclasses are run first.&lt;/li&gt;
&lt;li&gt;Then, all other Django-based tests (test cases based on SimpleTestCase, including TransactionTestCase) are run
  with no particular ordering guaranteed nor enforced among them.&lt;/li&gt;
&lt;li&gt;Then any other unittest.TestCase tests (including doctests) that may alter the database without restoring it to
  its original state are run.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is not of much concern most of the time but becomes important when you decide
to mix and match transaction and non-transaction based tests into one test suite.
As seen in &lt;a href="https://travis-ci.org/kiwitcms/Kiwi/jobs/321018491"&gt;Job #471.1&lt;/a&gt;
&lt;code&gt;tcms/xmlrpc/tests/test_serializer.py&lt;/code&gt; tests errored out! If you execute these tests
standalone they all pass! The root cause is that these serializer tests are based on
Django's &lt;code&gt;test.TestCase&lt;/code&gt; class and are executed after a &lt;code&gt;test.LiveServerTestCase&lt;/code&gt; class!&lt;/p&gt;
&lt;p&gt;The tests in &lt;code&gt;tcms/xmlrpc/tests/test_product.py&lt;/code&gt; will flush the database, removing all
records, including the ones from initial migrations. Then when &lt;code&gt;test_serializer.py&lt;/code&gt; is
executed it will call its factories which in turn rely on initial records being available
and produces an error because these records have been deleted!&lt;/p&gt;
&lt;p&gt;The reason for this is that &lt;strong&gt;pytest doesn't respect the order of execution for Django tests&lt;/strong&gt;!
As seen
in the build log above tests are executed in the order in which they were discovered!
My solution was not to use pytest (I don't need it for anything else)!&lt;/p&gt;
&lt;p&gt;At the moment I'm dealing with strange errors/segmentation faults when running Kiwi's tests
under Django 2.0. It looks like the http response has been closed before the client side
tries to read it. Why this happens I have not been able to figure out yet. Expect another
blog post when I do.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="Django"></category></entry><entry><title>How to configure MTU for the Docker network</title><link href="http://atodorov.org/blog/2017/12/08/how-to-configure-mtu-for-the-docker-network/" rel="alternate"></link><updated>2017-12-08T16:02:00+02:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-12-08:blog/2017/12/08/how-to-configure-mtu-for-the-docker-network/</id><summary type="html">&lt;p&gt;On one of my Jenkins slaves I've been experiencing problems when downloading
files from the network. In particular with &lt;code&gt;cabal update&lt;/code&gt; which fetches data
from hackage.haskell.org. As suggested by David Roble the problem and solution
lies in the MTU configured for the default &lt;code&gt;docker0&lt;/code&gt; interface!&lt;/p&gt;
&lt;p&gt;By default &lt;code&gt;docker0&lt;/code&gt; had MTU of &lt;code&gt;1500&lt;/code&gt; which should be lower than the
host &lt;code&gt;eth0&lt;/code&gt; MTU of 1400! To configure this before the docker daemon is started
place any non-default settings in &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;! For more information
head to
&lt;a href="https://docs.docker.com/engine/userguide/networking/default_network/custom-docker0/"&gt;https://docs.docker.com/engine/userguide/networking/default_network/custom-docker0/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category></entry><entry><title>4 Situational Leadership Styles</title><link href="http://atodorov.org/blog/2017/11/11/4-situational-leadership-styles/" rel="alternate"></link><updated>2017-11-11T01:22:00+02:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-11-11:blog/2017/11/11/4-situational-leadership-styles/</id><summary type="html">&lt;p&gt;At &lt;a href="http://seetest.org"&gt;SEETEST&lt;/a&gt; this year I visited only tracks related to
management and leadership. The presentation
&lt;em&gt;How good leadership makes you a great team player&lt;/em&gt; by Jeroen Rosink
was of particular interest to me. He talked about
&lt;a href="https://en.wikipedia.org/wiki/Situational_leadership_theory"&gt;situational leadership&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Leadership styles" src="/images/situational_leadership_styles_big.png" title="Leadership styles" /&gt;
&lt;em&gt;Image by Penn State University&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;According to Hersey &amp;amp; Blanchard each situation/person is different and it requires
a leader or manager to adjust their style in order to be successful. In particular
as a leader you have to approach each team, person and skill differently based on how
developed they are. In this context a skill can be any technical or non-technical
skill, a particular competence level required or anything really. The idea is that
for ever item that we would like to develop we would go through the cycle shown
on the image above.&lt;/p&gt;
&lt;h2&gt;Directing&lt;/h2&gt;
&lt;p&gt;Every new employee, team member, junior IT specialist starts with some directing.
This is the phase where you tell people what they have to do and how to do it
exactly. This is the phase of the almighty boss who provides the what, how,
why, when and where!&lt;/p&gt;
&lt;p&gt;In this phase an inexperienced(or new) person will figure out what is required
of them and give them detailed steps of how to achieve it.
Experienced team members will quickly find their bearings and transition out
of this phase.&lt;/p&gt;
&lt;h2&gt;Coaching&lt;/h2&gt;
&lt;p&gt;In this phase the individual has already acquired some skills but they are not
fully developed. In addition to tasks here we also focus at supporting the individual to improve
their skills and deepen the connection and trust between them and the leader. This
is the basis of creating strong commitment in the future.&lt;/p&gt;
&lt;p&gt;Think about coaches of sport teams. What they do is give direction in order to
create the best players/teams.&lt;/p&gt;
&lt;h2&gt;Supporting&lt;/h2&gt;
&lt;p&gt;This phase comes naturally after coaching. Here we can also make the parallel with
sport teams. In this phase team members are already competent in their skills but
somewhat inconsistent in their performance and not very committed to the
end goal of the team (e.g. winning, testing all bugs, delivering software on time).&lt;/p&gt;
&lt;p&gt;This is the phase in which
shared decisions are taken (what to test, how we should test, how to split the
tasks between team members) and in which teams are formed.
Here a leader must focus less on the particular tasks and much more on the
relationships within the group (don't forget the leader is also part of the group).&lt;/p&gt;
&lt;h2&gt;Delegating&lt;/h2&gt;
&lt;p&gt;This is the end phase in which we have individuals with strong skills and strong
commitment. They are able to work and progress on their own. The job of the leader
here is to monitor progress and still be part of some decisions. What I've seen
people who I believe were delegating do is mostly
reaffirm the decisions taken by the team.&lt;/p&gt;
&lt;p&gt;In this phase there's no need for the leader to focus on tasks and relationship
but rather high level goals and IMO providing opportunities for growth of
each individual team member. This is the phase where future leaders will come from.&lt;/p&gt;
&lt;h2&gt;What that means for the team ?&lt;/h2&gt;
&lt;p&gt;Notice the smaller section in the image above titled &lt;em&gt;Development Level&lt;/em&gt;!
While an individual or a team is going through the different phases of
leadership they also go through various stages of development. At the end
of the cycle we get individuals with very strong skills and very strong
commitment and work ethics.&lt;/p&gt;
&lt;h2&gt;What that means for the leader ?&lt;/h2&gt;
&lt;p&gt;(stats from presentation at the conference)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;54% of leaders can use only 1 style&lt;/li&gt;
&lt;li&gt;34% of leaders can use 2 styles&lt;/li&gt;
&lt;li&gt;11% of leaders can use 3 styles&lt;/li&gt;
&lt;li&gt;1% of leaders can use 4 styles&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means as leaders we have a lot to learn if we want to become effective.
We have to learn to recognize at what stage of development an organization and/or
a team is and what are the various stages of development of individual team members.
Then apply this model as appropriate.&lt;/p&gt;
&lt;p&gt;A side note: I am currently working with a group of young developers on an open source
project where all of them are pretty much at the beginning of their journey. They
lack almost all necessary technical skills that are needed to work on the project
and their profiles, including age are very similar to one another. I believe this is
an ideal situation to apply this model and see how it goes (expect results in a year or so).&lt;/p&gt;
&lt;p&gt;Note2: I will have 2 more developers joining the same project a bit later and I expect
one of them to be able to get up to speed faster (so far I have observed very
impressive self-development in them) so that will spice things up a bit :)&lt;/p&gt;
&lt;h2&gt;Bonus question&lt;/h2&gt;
&lt;p&gt;Do you remember
&lt;a href="http://atodorov.org/blog/2016/10/09/the-4-basic-communication-styles/"&gt;The 4 Basic Communication Styles&lt;/a&gt;
post from last year? I have the feeling that these styles are very much related
to the leadership strategies described above. For example Director is using the directing style,
Expresser sounds a lot like a coach to me and Harmonizer is using the supporting style.
Only a Thinker doesn't quite fit but on the other hand they can be quite self-driven
and not need supervision.&lt;/p&gt;
&lt;p&gt;I don't know if there's something here or I'm totally making things up. I'd love to
get some insights from psychologists, leadership experts and communication experts.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;p&gt;Here are a few basic articles to get you started&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://online.stu.edu/situational-leadership/"&gt;https://online.stu.edu/situational-leadership/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://johnkwhitehead.ca/situational-leadership/"&gt;https://johnkwhitehead.ca/situational-leadership/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.leadership-central.com/situational-leadership-theory.html#axzz4y4I4vrZ7"&gt;http://www.leadership-central.com/situational-leadership-theory.html#axzz4y4I4vrZ7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks for reading and happy testing (your leaderhip skills)!&lt;/p&gt;</summary><category term="fedora.planet"></category></entry><entry><title>Fallback to default values for NULL columns in Rust SQLite</title><link href="http://atodorov.org/blog/2017/10/27/fallback-to-default-values-for-null-columns-in-rust-sqlite/" rel="alternate"></link><updated>2017-10-27T16:12:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-10-27:blog/2017/10/27/fallback-to-default-values-for-null-columns-in-rust-sqlite/</id><summary type="html">&lt;p&gt;I have been working on code which changed its DB schema to add a NULL column
without a default value! The standard &lt;code&gt;row.get()&lt;/code&gt; from Rusqlite throws errors
because NULL is not a valid integer value.&lt;/p&gt;
&lt;p&gt;The solution is to use &lt;code&gt;row.get_checked()&lt;/code&gt; like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_checked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Interestingly enough I wasn't able to find clear information about this on the
Internet so here it is.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy hacking!&lt;/p&gt;</summary><category term="fedora.planet"></category></entry><entry><title>The ARCS model of motivational design</title><link href="http://atodorov.org/blog/2017/10/05/the-arcs-model-of-motivational-design/" rel="alternate"></link><updated>2017-10-05T10:40:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-10-05:blog/2017/10/05/the-arcs-model-of-motivational-design/</id><summary type="html">&lt;p&gt;&lt;img alt="Motivation" src="/images/motivation.jpg" title="Motivation" /&gt;&lt;/p&gt;
&lt;p&gt;The ARCS model is an instructional design method developed by John Keller
that focuses on motivation. ARCS is based on a research into best practices
and successful teachers and gives you tactics on how to evaluate your
lessons in order to build motivation right into them.&lt;/p&gt;
&lt;p&gt;I have conducted and oversaw quite a few trainings and I have not been impressed
with the success rate of those so this topic is very dear to me.
Success for me measures in the ability to complete the training and
learn the basis of a technical topic. And then gather the initial
momentum to continue developing your skills within the chosen field.
This is what I've been doing for myself and this is what I'd like to
see my students do.&lt;/p&gt;
&lt;p&gt;In his paper (I have a year 2000 printed copy from Cuba)
Keller argues that motivation is a product of four factors:
&lt;strong&gt;Attention, Relevance, Confidence and Satisfaction&lt;/strong&gt;. You need all of them
incorporated in your lessons and learning materials for them to be motivational.
I could argue that you need the same characteristics at work in order to
motivate people to do their job as you wish.&lt;/p&gt;
&lt;p&gt;Once you start a lesson you need to grab the audience &lt;strong&gt;Attention&lt;/strong&gt; so they
can listen to you. Then the topic needs to be &lt;strong&gt;relevant&lt;/strong&gt; to the audience
so they will continue listening to the end. This makes for a good start
but is not enough. &lt;strong&gt;Confidence&lt;/strong&gt; means for the audience to feel confident
they can perform all the necessary tasks on their own, that they have
what it takes to learn (and you have to build that). If they think they
can't make it from the start then it is a lost battle. And &lt;strong&gt;Satisfaction&lt;/strong&gt;
means the person feels that achievements are due to their own abilities and
hard work not due to external factors (work not demanding enough, luck, etc).&lt;/p&gt;
&lt;p&gt;If all of the above 4 factors are true then the audience should feel
personally motivated to learn because they can clearly understand the
benefit for themselves and they realize that everything depends on them.&lt;/p&gt;
&lt;p&gt;ARCS gives you a model to evaluate your target audience and lesson properties
and figure out tactics by which to address any shortcomings in the above 4 areas.&lt;/p&gt;
&lt;p&gt;Last Friday I hosted 2 training sessions: a Python and Selenium workshop
at HackConf and then a lecture about test case management and demo of
&lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt; before students at Pragmatic IT academy.
For both of them I used the simplified ARCS evaluation matrix.&lt;/p&gt;
&lt;p&gt;In this matrix the columns map to the ARCS areas while the rows map to
different parts of the lesson: audience, presentation media, exercise, etc.
Here's how I used them (I've mostly analyzed the audience).&lt;/p&gt;
&lt;h2&gt;Python &amp;amp; Selenium workshop&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Attention&lt;ul&gt;
&lt;li&gt;(+) this is an elective workshop&lt;/li&gt;
&lt;li&gt;(+) the topic is clear and the curricula is on GitHub&lt;/li&gt;
&lt;li&gt;(+) the title is catchy (Learn Python &amp;amp; Selenium in 6 hours)&lt;/li&gt;
&lt;li&gt;(+) I am well known in the industry&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Relevance&lt;ul&gt;
&lt;li&gt;(+) Basic Python practical skills, being able to write small programs,
  knowing the basic building blocks&lt;/li&gt;
&lt;li&gt;(+) Basic Selenium skills: finding and using elements&lt;/li&gt;
&lt;li&gt;(+) Basic Python test automation skills: writing simple tests and asserts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Confidence&lt;ul&gt;
&lt;li&gt;(+) each task has tests which need to report PASS at the end&lt;/li&gt;
&lt;li&gt;(-) need to use PyCharm IDE, unfamiliar with IDEs&lt;/li&gt;
&lt;li&gt;(-) not enough experience with programming or Linux&lt;/li&gt;
&lt;li&gt;(-) not enough experience with (automation) testing&lt;/li&gt;
&lt;li&gt;(-) all materials and exercises are in English&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Satisfaction&lt;ul&gt;
&lt;li&gt;(-) not being able to create a simple program&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From the above it was clear that I didn't need to spend much time on building
attention or relevance. The topic itself and the fact that these are skill which
can be immediately applied at work gave the workshop a huge boost. During the
opening part of my workshop I've stated "this training takes around 2 months,
I've seen some of you forking my GitHub repo so I know you are prepared. Let's
see how much you can do in 6 hours" which sets the challenge and was my attention
building moment. Then I reiterated that all skills are directly applicable in
daily work confirming the relevance part.&lt;/p&gt;
&lt;p&gt;I did need a confidence building strategy though. So having all the tests ready
meant evaluation was quick and easy. Anton (my assistant) and I promised to help
with the IDE and all other questions to counter the other items on the list.
During the course of the workshop I did quick code review of all participants
that managed to complete their tasks within the hour giving them quick tips on
how to perform or highlighting pieces of code/approaches that were different
from mine or that I found elegant or interesting. This was my confidence building
strategy. Code review and verbal praising also touches on the satisfaction
area, i.e. the participant gets the feeling they are doing well.&lt;/p&gt;
&lt;p&gt;My Satisfaction building strategy was kind of mixed. Before I read about ARCS
I wanted to give penalty points to participants who didn't complete on time and then
send them home after 3 fails. At the end I only said I will do this but didn't
do it.&lt;/p&gt;
&lt;p&gt;Instead I used the challenge statement from the attention phase and
turned that into a competition. The first 3 participants to complete their module tasks on time
were rewarded chocolates. With the agreement of the entire group the grand prize
was set to be a small box of the same chocolates and this would be awarded to
the person with the most chocolates (e.g. the one who's been in top 3 the most times).&lt;/p&gt;
&lt;blockquote class="twitter-tweet" data-lang="en"&gt;&lt;p lang="en" dir="ltr"&gt;Bistra is our winner. 4/5 times in top 3 &lt;a href="https://twitter.com/hashtag/Python?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#Python&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/Selenium?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#Selenium&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/testing?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#testing&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/HC17?src=hash&amp;amp;ref_src=twsrc%5Etfw"&gt;#HC17&lt;/a&gt; &lt;a href="https://t.co/vXrPhElbbW"&gt;pic.twitter.com/vXrPhElbbW&lt;/a&gt;&lt;/p&gt;&amp;mdash; Alexander Todorov (@atodorov_) &lt;a href="https://twitter.com/atodorov_/status/913787872032980993?ref_src=twsrc%5Etfw"&gt;September 29, 2017&lt;/a&gt;&lt;/blockquote&gt;

&lt;script async src="//platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

&lt;p&gt;I don't know if ARCS had anything to do with it but this workshop
was the most successful training I've ever done. 40% of the participants
managed to get at least one chocolate and at least 50% have completed all of
their tasks within the hour. Normally a passing rate on such training is
around 10 to 20 %.&lt;/p&gt;
&lt;p&gt;During the workshop we had 5 different modules which consisted of 10-15 minutes
explanation of Python basics (e.g. loops or if conditions), quick Q&amp;amp;A session
and around 30 minutes for working alone and code review. I don't think I was following
ARCS for each of the separate modules because I didn't have time to analyze them
individually. I gambled all my money on the introductory 10 minutes!&lt;/p&gt;
&lt;h2&gt;TCMS lecture&lt;/h2&gt;
&lt;p&gt;My second lecture for the day was about test case management. The audience was
students who are aspiring to become software testers and attending the
Software Testing training at Pragmatic. In my lecture (around 1 hour) I wanted
to explain what test management is, why it is important and also demo the
tool I'm working on - &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt;. The analysis looks like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Attention&lt;ul&gt;
&lt;li&gt;(+) the entire training was elective but&lt;/li&gt;
&lt;li&gt;(-) that particular lecture was mandatory. Students were not able to select
      what they are going to study&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Relevance&lt;ul&gt;
&lt;li&gt;(-) it may not be clear what TCMS is and why we need it&lt;/li&gt;
&lt;li&gt;(+) however students may sense that this is something work related since
      the entire training is&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Confidence&lt;ul&gt;
&lt;li&gt;(-) unknown UI, generally unfamiliar workflow&lt;/li&gt;
&lt;li&gt;(-) not enough knowledge how to write a Test Plan document or test cases&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Satisfaction&lt;ul&gt;
&lt;li&gt;(-) how to make sure new skills can be applied in practice&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I was in a medium need of a strategy to build attention. My opening was by introducing
myself to establish my professional level and introducing &lt;a href="http://kiwitcms.org"&gt;Kiwi TCMS&lt;/a&gt;
by saying it is the best open source test case management system to which I'm one of the
core maintainers.&lt;/p&gt;
&lt;p&gt;Then I had a medium need of a relevance building strategy. I did this by explaining what
test management is and why it is important. I've talked briefly about QA managers trying to
indirectly inspire the audience to aim for this position. I finished this part by telling
the students how a TCMS system helps the ordinary guy in their daily work - namely by
giving you a dashboard where you can monitor all the work you need to do, check your
progress, etc.&lt;/p&gt;
&lt;p&gt;I was in a strong need to build confidence. I did a 20-30 minutes demonstration where
I was writing a Test Plan and test cases and then pretending to execute them and marking bugs
and test results in the system. I told the students "you are my boss for today, tell me what
I need to test". So they instructed me to test the login functionality of the system
and we agreed on 5 different test cases. I described all of these into Kiwi TCMS and began
executing them. During execution I opened another browser window and did exactly what the
test case steps were asking for. There were some bugs so I promptly marked them as such and
I promised I will fix them.&lt;/p&gt;
&lt;p&gt;To build satisfaction I was planning on having the students write one test plan and some
test cases but we didn't have time for this. Their instructor promised they will be doing
more exercises and using Kiwi TCMS in the next 2 months but this remains to be seen.
I've wrapped my lecture by giving advise to use Kiwi TCMS as a portfolio building tool.
Since these students are newcomers to the QA industry their next priority will be looking
for a job. I've advised them to document their test plans and test cases into Kiwi TCMS
and then present these artifacts to future employers.
I've also told them they are more than welcome to test and report bugs against Kiwi TCMS
on GitHub and add these bugs to their portfolio!&lt;/p&gt;
&lt;p&gt;This is how I've applied ARCS for the first time. I like it and will continue to use it for
my trainings and workshops. I will try harder to make the application process more iterative
and apply the method not only to my opening speech but for all submodules as well!&lt;/p&gt;
&lt;p&gt;One thing that bothers me is can I apply the ARCS principles when doing a technical
presentation and how do they play together or clash with storytelling, communication style and
rhetoric (all topics I'm exploring for my public speaking). If you do have more experience
with these please share it in the comments below.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>Storytelling for test professionals</title><link href="http://atodorov.org/blog/2017/10/03/storytelling-for-test-professionals/" rel="alternate"></link><updated>2017-10-03T15:06:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-10-03:blog/2017/10/03/storytelling-for-test-professionals/</id><summary type="html">&lt;p&gt;This is a very condensed brief of an 8 hour workshop I visited
earlier this year held by Huib Schoots. You can find the
&lt;a href="https://www.dropbox.com/s/b2si3swfthtwtpi/Workshop%20Storytelling%20-%20RTC%202017%20-%20Huib%20Schoots.pdf?dl=1"&gt;slides here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Storytelling is the form in which people naturally communicate.
Understanding the building blocks of a story will help us
understand other people's motivations, serve as map for actions and emotions,
help uncover unknown perspectives and serve as source for inspiration.&lt;/p&gt;
&lt;p&gt;Stories stand on their own and have a beginning, middle and an end. There is a
main character and a storyline with development. Stories are authentic and
personal and often provocative and evoke emotions.&lt;/p&gt;
&lt;h2&gt;7 basic story plots&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Overcoming the Monster&lt;/li&gt;
&lt;li&gt;Rags to Riches&lt;/li&gt;
&lt;li&gt;The Quest&lt;/li&gt;
&lt;li&gt;Voyage and return&lt;/li&gt;
&lt;li&gt;Comedy&lt;/li&gt;
&lt;li&gt;Tragedy&lt;/li&gt;
&lt;li&gt;Rebirth&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From these we can derive the following
&lt;a href="http://annettesimmons.com/the-six-kinds-of-stories/"&gt;types of stories&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;6 types of stories&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Who am I (identity stories)&lt;/li&gt;
&lt;li&gt;Why am I here (motive and mission stories)&lt;/li&gt;
&lt;li&gt;Vision stories (the big picture)&lt;/li&gt;
&lt;li&gt;Future scenarios (imagining the future)&lt;/li&gt;
&lt;li&gt;Product stories (branding)&lt;/li&gt;
&lt;li&gt;Culture stories (a sum of other stories)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;12 Common Archetypes&lt;/h2&gt;
&lt;p&gt;Each story needs a hero and there are
&lt;a href="http://www.soulcraft.co/essays/the_12_common_archetypes.html"&gt;12 common archetypes&lt;/a&gt;
of heroes. More importantly you can also find these archetypes within your team and
organization. Read the link above to find out what their motto, core desire, goals,
fears and motives are. The 12 types are&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Innocent&lt;/li&gt;
&lt;li&gt;Everyman&lt;/li&gt;
&lt;li&gt;Hero&lt;/li&gt;
&lt;li&gt;Caregiver&lt;/li&gt;
&lt;li&gt;Explorer&lt;/li&gt;
&lt;li&gt;Rebel&lt;/li&gt;
&lt;li&gt;Lover&lt;/li&gt;
&lt;li&gt;Creator&lt;/li&gt;
&lt;li&gt;Jester&lt;/li&gt;
&lt;li&gt;Sage&lt;/li&gt;
&lt;li&gt;Magician&lt;/li&gt;
&lt;li&gt;Ruler&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;6 key elements of a story&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Who's the hero?&lt;/li&gt;
&lt;li&gt;What is their desire?&lt;/li&gt;
&lt;li&gt;What is stopping them?&lt;/li&gt;
&lt;li&gt;What is the turning point?&lt;/li&gt;
&lt;li&gt;What are their insights?&lt;/li&gt;
&lt;li&gt;What is the solution?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Dramatic structure and Freytag's pyramid&lt;/h2&gt;
&lt;p&gt;&lt;img alt="&amp;quot;Freytag's pyramid&amp;quot;" src="https://upload.wikimedia.org/wikipedia/commons/a/af/Freytags_pyramid.svg" title="Freytag's pyramid" /&gt;&lt;/p&gt;
&lt;p&gt;One of the most commonly used storytelling structures is the Freytag's Pyramid.
According to it each story has an exposition, rising action, climax, falling action
and resolution. I think this can be applied directly when preparing presentations
even technical ones.&lt;/p&gt;
&lt;h2&gt;The Hero's journey&lt;/h2&gt;
&lt;p&gt;Successful stories follow the 12 steps of the hero's journey&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ordinary world&lt;/li&gt;
&lt;li&gt;Call to adventure&lt;/li&gt;
&lt;li&gt;Refusal of the Call&lt;/li&gt;
&lt;li&gt;Meeting the mentor&lt;/li&gt;
&lt;li&gt;Crossing the threshold (after which the hero enters the Special world)&lt;/li&gt;
&lt;li&gt;Tests, allies and enemies&lt;/li&gt;
&lt;li&gt;Approach&lt;/li&gt;
&lt;li&gt;Ordeal, death &amp;amp; rebirth&lt;/li&gt;
&lt;li&gt;Rewards, seizing the sword&lt;/li&gt;
&lt;li&gt;The road back (to the ordinary world)&lt;/li&gt;
&lt;li&gt;Resurrection&lt;/li&gt;
&lt;li&gt;Return with elixir&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As part of the workshop we worked in groups and created a completely made up
story. Every person in the group was contributing couple of sentences from
their own experiences, trying to describe the particular step in the hero's journey.
At the end we told a story from the point of view of a single hero which was
a complete mash-up of moments that had nothing to do with each other. Still it
sounded very realistic and plausible.&lt;/p&gt;
&lt;h2&gt;Storytelling techniques&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://booksinbusiness.wordpress.com/2013/04/30/make-your-story-sticky-using-6-principles-s-u-c-c-e-s/"&gt;SUCCESS&lt;/a&gt;
means Simple, Unexpected, Concrete, Credible, Emotional, Stories. To use this
technique find the core of your idea, grab people's attention by
surprising them and make sure the idea can be understood and remembered later.
Find a way to make people believe in the idea so they can test it for themselves,
make them feel something to understand why this idea is important. Tell stories and
empower people to use an idea through narrative.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tweakyourslides.wordpress.com/tag/something-theyll-always-remember/"&gt;STAR&lt;/a&gt; means
Something They will Always Remember. A STAR Moment should be
Simple, Transferable, Audience-centered, Repeatable, and Meaningful.
There are
&lt;a href="http://www.wiley.com/legacy/email_templates/images/resonate.pdf"&gt;5 types of STAR moments&lt;/a&gt;:
memorable dramatization, repeatable sound bites, evocative visuals,
emotive storytelling, shocking statistics.&lt;/p&gt;
&lt;p&gt;To enhance our stories and presentations we should appeal to senses
(smell, sounds, sight, touch, taste) and make it visual.&lt;/p&gt;
&lt;p&gt;I will be using some of these techniques combined with others in my future
presentations and workshops. I'd love to be able to summarize all of them
into a short guide targeted at IT professionals but I don't know if this
is even possible.&lt;/p&gt;
&lt;p&gt;Anyway if you do try some of these techniques in your public speaking please
let me know how it goes. I want to hear what works for you and your audience
and what doesn't.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>More tests for login forms</title><link href="http://atodorov.org/blog/2017/10/02/more-tests-for-login-forms/" rel="alternate"></link><updated>2017-10-02T15:06:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-10-02:blog/2017/10/02/more-tests-for-login-forms/</id><summary type="html">&lt;p&gt;&lt;img alt="&amp;quot;Telenor's login form&amp;quot;" src="/images/telenor_login.png" title="Telenor's login form" /&gt;&lt;/p&gt;
&lt;p&gt;By now I probably have documented more test cases for login forms than anyone
else. You can check out my previous posts on the topic
&lt;a href="http://atodorov.org/blog/2016/04/12/how-to-hire-software-testers-pt-1/"&gt;here&lt;/a&gt; and
&lt;a href="http://atodorov.org/blog/2017/06/14/vmwares-favorite-login-form/"&gt;here&lt;/a&gt;. I give you a few more
examples.&lt;/p&gt;
&lt;p&gt;Test 01 and 02:
First of all let's start by saying that a "Remember me" checkbox should actually
remember the user and login them automatically on the next visit if checked. The
other way around if not checked. I don't think this has been mentioned previously!&lt;/p&gt;
&lt;p&gt;Test 03:
When there is a "Remember me" checkbox it should be selectable both with the mouse
and the keyboard. On my.telenor.bg the checkbox changes its image only when
clicked with the mouse. Also clicking the login button with Space doesn't work!&lt;/p&gt;
&lt;p&gt;Interestingly enough when I don't select "Remember me" at all and close then
revisit the page I am still able to access the internal pages of my account!
At this point I'm not quite sure what this checkbox does!&lt;/p&gt;
&lt;p&gt;Test 04:
Testing two factor authentication. I had the case where GitHub SMS didn't
arrive for over 24 hrs and I wasn't able to login. After requesting a new code
you can see the UI updating but I didn't receive another message. In this particular
case I received only one message with an already invalid code. So test for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how long does it take for the codes to expire&lt;/li&gt;
&lt;li&gt;is there a visual feedback indicating how many codes have been requested&lt;/li&gt;
&lt;li&gt;do latest code invalidates all the previous ones or all that have been unused
  still work&lt;/li&gt;
&lt;li&gt;what happens if I'm already logged in and somebody tries to access my account
  requesting additional codes which may or may not invalidate my login session?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test 05:
Check that confirmation codes, links, etc will actually expire after their
configured time. Kiwi TCMS had this problem which has been fixed in
&lt;a href="https://github.com/kiwitcms/Kiwi/commit/92162112bf2214b8eacf37ba3a796414b129a700#diff-353aa238f7ee459b1236e2a21f1142ba"&gt;version 3.32&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Test 06:
Is this a social-network-login only site? Then which of my profiles did I use?
Check that there is a working
&lt;a href="http://atodorov.org/blog/2013/03/14/django-social-auth-tip-reminder-of-login-provider/"&gt;social auth provider reminder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Test 07:
Check that there is an error message visible (e.g. wrong login credentials).
After the redesign Kiwi TCMS had stopped displaying this message and instead
presents the user with the login form again!&lt;/p&gt;
&lt;p&gt;Also checkout these
&lt;a href="http://testingchallenges.thetestingmap.org/index.php"&gt;testing challenges&lt;/a&gt;
by Claudiu Draghia where you can see many cases related to input field
validation! For example empty field, value too long, special characters in field, etc.
All of these can lead to issues depending on how login is implemented.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>Xiaomi's selfie bug</title><link href="http://atodorov.org/blog/2017/09/08/xiaomis-selfie-bug/" rel="alternate"></link><updated>2017-09-08T09:50:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-09-08:blog/2017/09/08/xiaomis-selfie-bug/</id><summary type="html">&lt;p&gt;Recently I've been exploring the user interface of a Xiaomi Redmi Note 4X
phone and noticed a peculiar bug, adding to my collection of
&lt;a href="http://atodorov.org/blog/2013/03/19/bug-in-nokia-software-shows-wrong-caller-id/"&gt;obscure phone bugs&lt;/a&gt;.
Sometimes when taking selfies the images
will not be saved in the correct orientation. Instead they will be saved as
if looking in the mirror and this is a bug!&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;quot;Samsung S5 front screen&amp;quot;" src="/images/samsung_s5_front_screen.jpg" title="Samsung S5 front screen" /&gt;&lt;/p&gt;
&lt;p&gt;While taking the selfie the display correctly acts as a mirror, see my personal
Samsung S5 (black) and the Xiaomi device (white).&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;quot;Xiaomi front screen&amp;quot;" src="/images/xiaomi_front_screen.jpg" title="Xiaomi front screen" /&gt;&lt;/p&gt;
&lt;p&gt;However when the image is saved and then viewed through the gallery application
there is a difference. The image below is taken with the Xiaomi device and there
have been no effects added to it except scaling and cropping. As you can see
the letters on the cereal box are mirrored!&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;quot;Xiaomi mirrored image&amp;quot;" src="/images/xiaomi_adi_mirrored.jpeg" title="Xiaomi mirrored image" /&gt;&lt;/p&gt;
&lt;p&gt;The symptoms of the bug are not quite clear as of yet. I've managed to reproduce at
around 50% rate so far. I've tried taking pictures during the day in direct sunlight
and in the shade, also in the evening under bad artificial lighting.
Taking photo of a child's face and then child plus varying number of adults.
Then photo of only 1 or more adults, heck I even made a picture of myself. I though that
lighting or the number of faces and their age have something to do with this bug
but so far I'm not getting consistent results. Sometimes the images turn out OK
and other times they don't regardless of what I take a picture of.&lt;/p&gt;
&lt;p&gt;I also took a picture of the same cereal box, under the same conditions as above but
not capturing the child's face and the image came out not mirrored. The only clue
that seems to hold true so far is that you need to have people's faces in the picture
for this bug to reproduce but that isn't an edge case when taking selfies, right?&lt;/p&gt;
&lt;p&gt;I've also compared the results with my Samsung S5 (Android version 6.0.1) and BlackBerry Z10 devices
and both work as expected: while taking the picture the display acts as a mirror
but when viewing the saved image it appears in normal orientation. On S5 there is
also a clearly visible "Processing" progress bar while the picture is being saved!&lt;/p&gt;
&lt;p&gt;For reference the system information is below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;Model number: Redmi Note 4X
Android version: 6.0 MRA58K
Android security patch level: 2017-03-01
Kernel version: 3.18.22+
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I'd love if somebody
from Xiaomi's engineering department looks into this and sends me a root cause analysis
of the problem.&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing! Oh and btw this is my breakfast, not hers!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>Speeding up Rust builds inside Docker</title><link href="http://atodorov.org/blog/2017/08/30/speeding-up-rust-builds-inside-docker/" rel="alternate"></link><updated>2017-08-30T09:00:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-08-30:blog/2017/08/30/speeding-up-rust-builds-inside-docker/</id><summary type="html">&lt;p&gt;Currently &lt;a href="https://github.com/rust-lang/cargo/pull/3567"&gt;it is not possible&lt;/a&gt;
to instruct &lt;code&gt;cargo&lt;/code&gt;, the Rust package manager, to build only the dependencies
of the software you are compiling! This means you can't easily pre-install
build dependencies. Luckily you can workaround this with &lt;code&gt;cargo build -p&lt;/code&gt;!
I've been using this Python script to parse &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;span class="filename"&gt;parse-cargo-toml.py&lt;/span&gt;&lt;pre&gt;&lt;span class="c"&gt;#!/usr/bin/env python&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;toml&lt;/span&gt;

&lt;span class="n"&gt;_pwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;cargo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_pwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Cargo.toml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;dependencies&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;dev-dependencies&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cargo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;cargo build -p &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;and then inside my &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="x"&gt;RUN mkdir /bdcs-api-rs/&lt;/span&gt;
&lt;span class="x"&gt;COPY parse-cargo-toml.py /bdcs-api-rs/&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="x"&gt; Manually install cargo dependencies before building&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="x"&gt; so we can have a reusable intermediate container.&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="x"&gt; This workaround is needed until cargo can do this by itself:&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="x"&gt; https://github.com/rust-lang/cargo/issues/2644&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="x"&gt; https://github.com/rust-lang/cargo/pull/3567&lt;/span&gt;
&lt;span class="x"&gt;COPY Cargo.toml /bdcs-api-rs/&lt;/span&gt;
&lt;span class="x"&gt;WORKDIR /bdcs-api-rs/&lt;/span&gt;
&lt;span class="x"&gt;RUN python ./parse-cargo-toml.py | while read cmd; do \&lt;/span&gt;
&lt;span class="x"&gt;        &lt;/span&gt;&lt;span class="p"&gt;$&lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="x"&gt;;                                    \&lt;/span&gt;
&lt;span class="x"&gt;    done&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It doesn't take into account the version constraints specified in &lt;code&gt;Cargo.toml&lt;/code&gt; but
is still able to produce an intermediate docker layer which I can use to
&lt;a href="http://atodorov.org/blog/2017/08/07/faster-travis-ci-tests-with-docker-cache/"&gt;speed-up my tests by caching the dependency compilation part&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As seen in the &lt;a href="https://travis-ci.org/weldr/bdcs-api-rs/builds/268489460#L1173"&gt;build log&lt;/a&gt;,
lines 1173-1182, when doing &lt;code&gt;cargo build&lt;/code&gt; it downloads and compiles &lt;code&gt;chrono v0.3.0&lt;/code&gt; and
&lt;code&gt;toml v0.3.2&lt;/code&gt;. The rest of the dependencies are already available. The logs also show
that after Job #285 the build times dropped from 16 minutes down to 3-4 minutes due to
Docker caching. This would be even less if the cache is kept locally!&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry><entry><title>Code coverage from Nightmare.js tests</title><link href="http://atodorov.org/blog/2017/08/12/code-coverage-from-nightmarejs-tests/" rel="alternate"></link><updated>2017-08-12T18:11:00+03:00</updated><author><name>Alexander Todorov</name></author><id>tag:atodorov.org,2017-08-12:blog/2017/08/12/code-coverage-from-nightmarejs-tests/</id><summary type="html">&lt;p&gt;In this article I'm going to walk you through the steps required
to collect code coverage when running an end-to-end test suite
against a React.js application.&lt;/p&gt;
&lt;p&gt;The application under test looks like this&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en-us&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;layout-pf layout-pf-fixed&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- js dependencies skipped --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./dist/main.js?0ca4cedf3884d3943762&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It is served as an &lt;code&gt;index.html&lt;/code&gt; file and a &lt;code&gt;main.js&lt;/code&gt; file which intercepts
all interactions from the user and sends requests to the backend API when
needed.&lt;/p&gt;
&lt;p&gt;There is an existing unit-test suite which loads the individual components
and tests them in isolation.
&lt;a href="https://twitter.com/atodorov_/status/886881560754102272"&gt;Apparently people do this&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;There is also an end-to-end test suite which does the majority of the testing.
It fires up a browser instance and interacts with the application. Everything
runs inside Docker containers providing a full-blown production-like environment.
They look like this&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should switch to Edit Recipe page - recipe creation success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nightmare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Nightmare&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;nightmare&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btnCreateRecipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipesPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btnCreateRecipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogRootElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;block&amp;#39;&lt;/span&gt;
      &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createRecipePage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createRecipePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createRecipePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;varRecName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createRecipePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputDescription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createRecipePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;varRecDesc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createRecipePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btnSave&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editRecipePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentListItemRootElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editRecipePage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentListItemRootElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// remove this!&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// here goes coverage collection helper&lt;/span&gt;
      &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// remove this!&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The browser interaction is handled by Nightmare.js (sort of like Selenium) and
the test runner is Jest.&lt;/p&gt;
&lt;h2&gt;Code instrumentation&lt;/h2&gt;
&lt;p&gt;The first thing we need is to instrument the application code to provide coverage
statistics. This is done via &lt;code&gt;babel-plugin-istanbul&lt;/code&gt;. Because unit-tests are
executed a bit differently we want to enable conditional instrumentation. In reality
for unit tests we use &lt;code&gt;jest --coverage&lt;/code&gt; which enables istanbul on the fly and having
the code already instrumented breaks this. So I have the following in &lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--with-coverage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;babelConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;istanbul&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;and then build my application with &lt;code&gt;node run build --with-coverage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can execute &lt;code&gt;node run start --with-coverage&lt;/code&gt;, open the JavaScript console
in your browser and inspect the &lt;code&gt;window.__coverage__&lt;/code&gt; variable. If this is defined
then the application is instrumented correctly.&lt;/p&gt;
&lt;h2&gt;Fetching coverage information from within the tests&lt;/h2&gt;
&lt;p&gt;Remember that &lt;code&gt;main.js&lt;/code&gt; from the beginning of this post? It lives inside &lt;code&gt;index.html&lt;/code&gt;
which means everything gets downloaded to the client side and executed there.
When running the end-to-end test suite that is the browser instance which is controlled
via Nightmare. &lt;strong&gt;You have to pass &lt;code&gt;window.__coverage__&lt;/code&gt; from the browser scope back to
nodejs scope via &lt;code&gt;nightmare.evaluate()&lt;/code&gt;&lt;/strong&gt;! I opted to directly save the coverage data
on the file system and make it available to coverage reporting tools later!&lt;/p&gt;
&lt;p&gt;My coverage collecting snippet looks like this&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nx"&gt;nightmare&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__coverage__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// this executes in browser scope&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// terminate the Electron (browser) process&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cov&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this executes in Node scope&lt;/span&gt;
    &lt;span class="c1"&gt;// handle the data passed back to us from browser scope&lt;/span&gt;
    &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;strCoverage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cov&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;crypto&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sha256&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strCoverage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hex&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;strCoverage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// the callback from the test&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nightmare returns &lt;code&gt;window.__coverage__&lt;/code&gt; from browser scope back to nodejs scope
and we save it under &lt;code&gt;/tmp&lt;/code&gt; using a hash value of the coverage data as the file
name.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side note:&lt;/em&gt; I do have about 40% less coverage files than number of test cases.
This means some test scenarios exercise the same code paths. Storing the individual
coverage reports under a hashed file name makes this very easy to see!&lt;/p&gt;
&lt;p&gt;Note that in my coverage handling code I also call &lt;code&gt;.end()&lt;/code&gt; which will terminate
the browser instance and also execute the &lt;code&gt;done()&lt;/code&gt; callback which is being passed
as parameter to the test above! This is important because it means we had to update
the way tests were written. In particular the Nightmare method sequence doesn't
have to call &lt;code&gt;.end()&lt;/code&gt; and &lt;code&gt;done()&lt;/code&gt; except in the coverage handling code. The
coverage helper must be the last code executed inside the body of the last
&lt;code&gt;.then()&lt;/code&gt; method. This is usually after all assertions (expectations) have been met!&lt;/p&gt;
&lt;p&gt;Now this coverage helper needs to be part of every single test case so I
wanted it to be a one line function, easy to copy&amp;amp;paste! All my attempts to
move this code inside a module have been futile. I can get the module loaded
but it kept failing with
&lt;code&gt;Unhandled promise rejection (rejection id: 1): cov_23rlop1885 is not defined&lt;/code&gt;;`&lt;/p&gt;
&lt;p&gt;At the end I've resorted to this simple hack&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utils/coverage.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Shout-out to &lt;a href="http://krasimirtsonev.com/"&gt;Krasimir Tsonev&lt;/a&gt; who joined me on a two
days pairing session to figure this stuff out. Too bad we couldn't quite figure it
out. If you do please send me a pull request!&lt;/p&gt;
&lt;h2&gt;Reporting the results&lt;/h2&gt;
&lt;p&gt;All of these &lt;code&gt;coverage-*.json&lt;/code&gt; files are directly consumable by &lt;code&gt;nyc&lt;/code&gt; - the
coverage reporting tool that comes with the Istanbul suite! I mounted
&lt;code&gt;.nyc_output/&lt;/code&gt; directly under &lt;code&gt;/tmp&lt;/code&gt; inside my Docker container so I could&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;nyc report
nyc report --reporter=lcov | codecov
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can also modify the unit-test command to
&lt;code&gt;jest --coverage --coverageReporters json --coverageDirectory .nyc_output&lt;/code&gt; so it
produces a &lt;code&gt;coverage-final.json&lt;/code&gt; file for &lt;code&gt;nyc&lt;/code&gt;. Use this if you want to combine
the coverage reports from both test suites.&lt;/p&gt;
&lt;p&gt;Because I'm using Travis CI the two test suites are executed independently and
there is no easy way to share information between them. Instead I've switched
from Coveralls to CodeCov which is smart enough to merge coverage submissions
coming from multiple jobs on the same git commits. You can compare the commit
&lt;a href="https://codecov.io/gh/atodorov/welder-web/commit/46556808e42a21f48d008ced2d53ffe176c01b6d"&gt;submitting only unit-test results&lt;/a&gt;
with the one
&lt;a href="https://codecov.io/gh/atodorov/welder-web/commit/15f437477c17b63797cdb2455f1371336d7dc0e5"&gt;submitting coverage from both test suites&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All of the above steps are put into practice in
&lt;a href="https://github.com/weldr/welder-web/pull/136"&gt;PR #136&lt;/a&gt; if you want to check them out!&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy testing!&lt;/p&gt;</summary><category term="fedora.planet"></category><category term="QA"></category></entry></feed>