<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Shing Lyu - Articles</title>
    <description>Shing Lyu&apos;s Portfolio and Blog</description>
    <link>https://shinglyu.com</link>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
      
      <item>
        <title>Lessons learned in writing my first book</title>
        <description>&lt;p&gt;&lt;img src=&quot;/blog_assets/book_cover.jpg&quot; alt=&quot;book cover&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;You might have noticed that I didn’t update this blog frequently in the past year. It’s not because I’m lazy, but I focused all my creative energy on writing this book: &lt;a href=&quot;https://www.apress.com/gp/book/9781484255988&quot;&gt;Practical Rust Projects&lt;/a&gt;. The book is now available on &lt;a href=&quot;https://www.apress.com/gp/book/9781484255988&quot;&gt;Apress&lt;/a&gt;, &lt;a href=&quot;https://www.amazon.com/Practical-Rust-Projects-Computing-Applications/dp/1484255984&quot;&gt;Amazon&lt;/a&gt; and &lt;a href=&quot;https://learning.oreilly.com/library/view/practical-rust-projects/9781484255995/&quot;&gt;O’Reilly&lt;/a&gt;. In this post, I’ll share some of the lessons I learned in writing this book.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Although I’ve been writing Rust for quite a few years, I haven’t really studied the internals of the Rust language itself. Many of the Rust enthusiasts whom I know seem to be having much fun appreciating how the language is designed and built. But I take more joy in using the language to build tangible things. Therefore, I’ve been thinking about writing a cookbook-style book on how to build practical projects with Rust, ever since I finished the video course &lt;a href=&quot;https://www.packtpub.com/application-development/building-reusable-code-rust-video&quot;&gt;Building Reusable Code with Rust&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Out of my surprise, I received an email from Steve Anglin, an acquisition editor from Apress, in April 2019. He initially asked me to write a book on the &lt;a href=&quot;https://github.com/RustPython/RustPython&quot;&gt;RustPython&lt;/a&gt; project. But the project was still growing rapidly thanks to the contributors. I’ve already lost grip on the overall architecture, so I can’t really write much about it. So I proposed the topic I have in mind to Steve. Fortunately, the editorial board accepted my proposal, and we decided to write two books: one for general Rust projects and one for web-related Rust projects.&lt;/p&gt;

&lt;p&gt;Since this is my first time writing a book that will be published in physical form (or as The Rust Book put it, “dead tree form”), I learned quite a lot throughout the process. Hopefully, these points will help you if you are considering or are already writing your own book.&lt;/p&gt;

&lt;h2 id=&quot;the-pomodoro-technique-works&quot;&gt;The Pomodoro Technique works!&lt;/h2&gt;
&lt;p&gt;It might be tempting to write a waterfall-style book writing plan, something along the lines of “I’ll write one section per day”. But there is a fundamental flaw with this approach: not all sections are of the same length and difficulty. A section about how to setup software might be very mechanical and easy to write, but a section about the history of a piece of software might take you days of research. Therefore, you can’t reliably predict how long it will take to finish a chapter, resulting in fear of starting and procrastination.&lt;/p&gt;

&lt;p&gt;I find the Pomodoro Technique to be a constructive alternative in structuring my writing plan. The Pomodoro Technique is a time management method where you break your work into 25 minutes intervals and rest in between. Instead of saying, “I’ll finish this chapter by today”, say “I’ll do 2 Pomodoro sessions (25 x 2 = 50 min) today”. This way, no matter if you are making good progress or not, you know you are committing enough effort into the book writing project. A good side effect is that once I started writing, I got into the flow and ended up writing more Pomodoro sessions then I planned.&lt;/p&gt;

&lt;h2 id=&quot;an-outline-is-important&quot;&gt;An Outline is important&lt;/h2&gt;
&lt;p&gt;I usually write freely without and outline when I write my blog. But writing a book is a completely different thing. An outline will help you structure the content much better and avoid missing important topics. I usually start with a very high-level chapter outline, then add a section outline before I start writing each section. It’s nice to write the outline for all the chapters before starting. When you are writing Chapter 1 and suddenly have an idea about something in Chapter 3, you can quickly add that to the Chapter 3 outline as a reminder.&lt;/p&gt;

&lt;p&gt;Example code is a crucial part of a programming book. It’s also beneficial to list down each step in the outline while you develop the example code. Because if you write all the example code in one go, you’ll forget about many steps that are not in the final code when you revisit it. For example,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Installing an external dependency&lt;/li&gt;
  &lt;li&gt;Important trail-and-error you went through&lt;/li&gt;
  &lt;li&gt;Informative compiler errors and warnings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all key points that need to be mentioned, but they don’t show up in the final code. Although you can dig that up by going through the git history, it’s still easier to write them down during development.&lt;/p&gt;

&lt;h2 id=&quot;version-control-and-latex&quot;&gt;Version control and LaTex&lt;/h2&gt;

&lt;p&gt;Version control is not only for code. The draft will go through multiple reviews and revise passes. You’ll usually have to revise the previous chapter while you are writing the next one. So use a format that is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff&lt;/code&gt;-friendly is very helpful. I use an unofficial LaTex template provided by Apress, with my own modifications. There are some random LaTex tips:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A professional production team lays out my page for softcover prints. So the image positioning from LaTex will not be the final one. But it’s still crucial to place the image after they are referenced. So I sometimes need to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\clearpage&lt;/code&gt; to force the image to appear on the next page.&lt;/li&gt;
  &lt;li&gt;I render the draft on A4 paper, but the final book is narrower and has a 65 character limit on the source code line length. Therefore, the production team has to wrap the code lines in many places. But since they are not Rust experts, they sometimes wrap the line in a non-idiomatic way. So it’s worth checking with your editor what is the source code width limit.&lt;/li&gt;
  &lt;li&gt;Take advantage of shell scripts to help you with writing. I created scripts for:
    &lt;ul&gt;
      &lt;li&gt;Scan the LaTex code to find missing code listing files or images.&lt;/li&gt;
      &lt;li&gt;Linting the code examples.&lt;/li&gt;
      &lt;li&gt;Linting common grammatical or style errors.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;use-a-grammar-checker-multiple-times&quot;&gt;Use a grammar checker, multiple times&lt;/h2&gt;
&lt;p&gt;I’m not a native English speaker, so I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aspell&lt;/code&gt; and Grammarly to check my grammar (disclaimer: Grammarly does not sponsor me). GNU Aspell is an open-source spell checker for the command line. Although it mostly checks for spelling, not grammar, its command-line interface and keyboard control is much more efficient than clicking the mouse. So I use Aspell to fix apparent typos. Then I copy-paste the LaTex source code into Grammarly’s web interface for a more thorough grammar check. However, many of the latex annotations like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\texttt{code formatting}&lt;/code&gt; breaks the sentence, so Grammarly misses some sentences. So after the first round of Grammarly check, I render the LaTex code into PDF and use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdftotext&lt;/code&gt; command to extract the rendered text from the PDF. I then run Grammarly through this text again.&lt;/p&gt;

&lt;p&gt;Of course, to fix this problem once and for all, improving the writing skill is critical. I came across this &lt;a href=&quot;https://developers.google.com/tech-writing&quot;&gt;Google Technical Writing Course&lt;/a&gt; after I finish this book, which helped me a lot in the writing process of my second book.&lt;/p&gt;

&lt;h2 id=&quot;get-the-book-now&quot;&gt;Get the book now&lt;/h2&gt;

&lt;p&gt;In this post, I focused on a few practical tips about book writing. I can maybe write another post about my LaTex setup and my observation about the Rust ecosystem. Leave a message using the “Message me” at the bottom right to let me know if that will be interesting to you.&lt;/p&gt;

&lt;p&gt;Please grab a copy of the book at the book store of your choice and let me know what you think:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.apress.com/gp/book/9781484255988&quot;&gt;Apress&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.amazon.com/Practical-Rust-Projects-Computing-Applications/dp/1484255984&quot;&gt;Amazon&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learning.oreilly.com/library/view/practical-rust-projects/9781484255995/&quot;&gt;O’Reilly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sun, 05 Apr 2020 08:42:16 +0000</pubDate>
        <link>https://shinglyu.com/web/2020/04/05/lessons-learned-in-writing-my-first-book.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2020/04/05/lessons-learned-in-writing-my-first-book.html</guid>
      </item>
      
    
    
    
    
    
    
    
    
    
    
    
    
    
      
      <item>
        <title>Download JavaScript Data as Files on the Client Side</title>
        <description>&lt;p&gt;When building websites or web apps, creating a “Download as file” link is quite useful. For example if you want to allow user to export some data as JSON, CSV or plain text files so they can open them in external programs or load them back later. Usually this requires a web server to format the file and serve it. But actually you can export arbitrary JavaScript variable to file entirely on the client side. I have implemented that function in one of my project, MozApoy, and here I’ll explain how I did that.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;First, we create a link in HTML&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;a id=&quot;download_link&quot; download=&quot;my_exported_file.txt&quot; href=”” &amp;gt;Download as Text File&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;download&lt;/code&gt; attribute will be the filename for your file. It will look like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/js_download_as_file/plain.png&quot; alt=&quot;plain text file download&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Notice that we keep the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;href&lt;/code&gt; attribute blank. Traditionally we fill this attribute with a server-generated file path, but this time we’ll assign it dynamically generate the link using JavaScript.&lt;/p&gt;

&lt;p&gt;Then, if we want to export the content of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text&lt;/code&gt; variable as a text file, we can use this JavaScript code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var text = &apos;Some data I want to export&apos;;
var data = new Blob([text], {type: &apos;text/plain&apos;});

var url = window.URL.createObjectURL(data);

document.getElementById(&apos;download_link&apos;).href = url;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The magic happens on the third line, the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.URL.createObjectURL()&lt;/code&gt; API&lt;/a&gt; takes a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Blob&quot;&gt;Blob&lt;/a&gt; and returns an URL to access it. The URL lives as long as the document in the window on which it was created. Notice that you can assign the type of the data in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new Blob()&lt;/code&gt; constructor. If you assign the correct format, the browser can better handle the file. Other commonly seen formats include &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/json&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text/csv&lt;/code&gt;. For example, if we name the file as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.csv&lt;/code&gt; and give it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type: &apos;text/csv&apos;&lt;/code&gt;, Firefox will recognize it as “CSV document” and suggest you open it with LibreOffice Calc.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/js_download_as_file/csv.png&quot; alt=&quot;csv file download&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And in the last line we assign the url to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;a/&amp;gt;&lt;/code&gt; element’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;href&lt;/code&gt; attribute, so when the user clicks on the link, the browser will initiate an download action (or other default action for the specific file type.)&lt;/p&gt;

&lt;p&gt;Everytime you call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createObjectURL()&lt;/code&gt;, a new object URL will be created, which will use up the memory if you call it many times. So if you don’t need the old URL anymore, you should call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;revokeObjectURL()&lt;/code&gt; API to free them.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var url = window.URL.createObjectURL(data);
window.URL.revokeObjectURL(url);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a simple trick to let your user download files without setting up any server. If you want to see it in action, you can check out this &lt;a href=&quot;http://codepen.io/anon/pen/qZQBmN?editors=1010&quot;&gt;CodePen&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Sat, 09 Feb 2019 09:28:39 +0000</pubDate>
        <link>https://shinglyu.com/web/2019/02/09/js_download_as_file.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2019/02/09/js_download_as_file.html</guid>
      </item>
      
    
    
    
    
    
    
    
      
      <item>
        <title>Counting your contribution to a git repository</title>
        <description>&lt;p&gt;Sometimes you may wonder, how many commits or lines of code did I contributed to a git repository? Here are some easy one liners to help you count that.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;number-of-commits&quot;&gt;Number of commits&lt;/h1&gt;

&lt;p&gt;Let’s start with the easy one: counting the number of commits made by one user.&lt;/p&gt;

&lt;p&gt;The easiest way is to run&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git shortlog &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This gives you a list of commit counts by user:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2  Grant Lindberg
9  Jonathan Hao
2  Matias Kinnunen
65  Shing Lyu
4  Shou Ya
1  wildsky
1  wildskyf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(The example comes from &lt;a href=&quot;https://github.com/shinglyu/QuantumVim&quot;&gt;shinglyu/QuantumVim&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;If you only care about one user you can use&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git rev-list HEAD &lt;span class=&quot;nt&quot;&gt;--author&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Shing Lyu&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--count&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;, which prints &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;65&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s explain how this works:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rev-list HEAD&lt;/code&gt; will list the commit objects in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HEAD&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--author=&quot;Shing Lyu&quot;&lt;/code&gt; will filter out only the commits made by the author Shing Lyu&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--count&lt;/code&gt; counts the number of commits. You can pipe it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;| wc -l&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;count-the-line-of-insertion-and-deletions-by-a-user&quot;&gt;Count the line of insertion and deletions by a user&lt;/h1&gt;

&lt;p&gt;Insertion and deletions are a little bit tricker. This is what I came up with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git log &lt;span class=&quot;nt&quot;&gt;--author&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Shing &lt;span class=&quot;nt&quot;&gt;--pretty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tformat: &lt;span class=&quot;nt&quot;&gt;--numstat&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;^-&apos;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{ add+=$1; remove+=$2 } END { print add, remove }&apos;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This might seem a little bit daunting, but we’ll break it up into steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log --author=&quot;Shing Lyu&quot;&lt;/code&gt; list the commits by Shing Lyu, in the following format:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;commit 6966b2c969cbf62029792221bf124ed75ee2c640
Author: Shing Lyu 
Date:   Sat Nov 18 17:01:25 2017 +0100

    Added Ctrl+z to close all system tabs

commit f4710cc3a2efdc63c7caf3ec04d504912ad20a93
Author: Shing Lyu 
Date:   Sat Nov 18 15:58:20 2017 +0100

    Bump version and diable jpm packaging
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--numstat&lt;/code&gt; will give us the line added and removed per file per commit:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;commit 6966b2c969cbf62029792221bf124ed75ee2c640
Author: Shing Lyu 
Date:   Sat Nov 18 17:01:25 2017 +0100

    Added Ctrl+z to close all system tabs

    1       0       README.md
    10      0       manifest.json
    6       1       package.sh
    35      0       vim-background.js
    4       1       vim.js

commit f4710cc3a2efdc63c7caf3ec04d504912ad20a93
Author: Shing Lyu 
Date:   Sat Nov 18 15:58:20 2017 +0100

    Bump version and diable jpm packaging

    1       1       manifest.json
    3       3       package.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;We don’t really need the commit, Author, Date and commit message fields, so we use an empty formatting string to get rid of them: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--pretty=tformat:&lt;/code&gt;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1       0       README.md
10      0       manifest.json
6       1       package.sh
35      0       vim-background.js
4       1       vim.js
1       1       manifest.json
3       3       package.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you add some non-text files, e.g. png image files, the insertion/deletion count might be represented as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;- - foo.png&lt;/code&gt;. Therefore we filter them out with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep -v &apos;^-&apos;&lt;/code&gt;. If you are not familiar with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v&lt;/code&gt; means reverse match (i.e. find those lines that does NOT match the patter). The pattern &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^-&lt;/code&gt; means lines staring with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;. (This part is optional if you pipe to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt; seems to ignore non-numeric character while doing the math.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Finally we pipe it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt; for summing. Even if you are not familiar with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt;, this part is pretty self-explanatory:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{ add+=$1; remove+=$2 } END { print add, remove }&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;We add column one (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$1&lt;/code&gt;) to the variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;, and column two (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$2&lt;/code&gt;) to the variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt;, then we print them out. This gives us an output like so:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;936 260 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;other-alternatives&quot;&gt;Other alternatives&lt;/h1&gt;

&lt;p&gt;There are many other off-the-shelf scrips that will help you calculate contribution statistics. Like &lt;a href=&quot;https://github.com/arzzen/git-quick-stats&quot;&gt;git-quick-stats&lt;/a&gt;, &lt;a href=&quot;https://github.com/casperdcl/git-fame&quot;&gt;git-fame&lt;/a&gt; and &lt;a href=&quot;https://github.com/oleander/git-fame-rb&quot;&gt;git-fame-rb&lt;/a&gt;. But if you only want a quick-and-easy solution please give it a try.&lt;/p&gt;

</description>
        <pubDate>Tue, 25 Dec 2018 11:00:27 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/12/25/counting-your-contribution-to-a-git-repository.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/12/25/counting-your-contribution-to-a-git-repository.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>New Rust Course - Building Reuseable Code with Rust</title>
        <description>&lt;p&gt;My first ever video course is now live on &lt;a href=&quot;https://www.udemy.com/building-reusable-code-with-rust/&quot;&gt;Udemy&lt;/a&gt;, &lt;a href=&quot;https://www.oreilly.com/library/view/building-reusable-code/9781788399524/&quot;&gt;Safari Books&lt;/a&gt; and &lt;a href=&quot;https://www.packtpub.com/application-development/building-reusable-code-rust-video&quot;&gt;Packt&lt;/a&gt;. It really took me a long time and I’d love to share with you what I’ve prepared for you.&lt;/p&gt;

&lt;h1 id=&quot;whats-this-course-about&quot;&gt;What’s this course about?&lt;/h1&gt;
&lt;p&gt;This course is about the &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; programming language, but it’s not those general introductory course on basic Rust syntax. This course focus on the code reuse aspect of the Rust language. So we won’t be touch every language feature, but we’ll help you understand how a selected set of features will help you achieve code reuse.&lt;/p&gt;

&lt;h1 id=&quot;whats-so-special-about-it&quot;&gt;What’s so special about it?&lt;/h1&gt;

&lt;p&gt;Since these course is not a general introduction course, it is structured in a way that is bottom-up and help you learn how the features are actually used out in the wild.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;a-bottom-up-approach&quot;&gt;A bottom up approach&lt;/h2&gt;
&lt;p&gt;We started from the most basic programming language construct: loops, iterators and functions. Then we see how we can further generalize functions and data structures (structs and enums) using generics. With these tools, we can avoid copy-pasting and stick to the DRY (Don’t Repeat Yourself) principle.&lt;/p&gt;

&lt;p&gt;But simply avoiding repeated code snippet is not enough. What comes next naturally is to define a clear interface, or internal API between the modules (in a general sense, not the Rust &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt;). This is when traits comes in handy. Traits help you define and enforce interfaces. We’ll also discuss the performance impact on static dispatch vs. dynamic dispatch by using generics and trait object.&lt;/p&gt;

&lt;p&gt;Finally we talk about more advanced (i.e. you shouldn’t use it unless necessary) tool like macros, which will help do crazier things by tapping directly into the compiler. You can write function-like macros that can help you reuse code that needs lower level access. You can also create custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;derive&lt;/code&gt; with macros.&lt;/p&gt;

&lt;p&gt;Finally, with these tools at hand, we can package our code into modules (Rust &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod&lt;/code&gt;), which can help you define a hierarchical namespace. We can then organize these modules into Crates, which are software packages or libraries that can contain multiple files. When you reach this level, you can already consume and produce libraries and frameworks and work with a team of Rust developers.&lt;/p&gt;

&lt;h2 id=&quot;a-guided-tour-though-the-std&quot;&gt;A guided tour though the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;It’s very easy to learn a lot of syntax, but never understand how they are used in real life. In each section, we’ll guide you through how these programming tools are used in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt;, or the Rust standard library. Standard libraries are the extreme form of code reuse, you are reusing code that is produced by the core language team. You’ll be able to see how these features are put to real use in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std&lt;/code&gt; to solve their code reuse needs.&lt;/p&gt;

&lt;p&gt;We’ll also show you how you can publish your code onto &lt;a href=&quot;https://crates.io/&quot;&gt;crates.io&lt;/a&gt;, Rust’s package registry. Therefore you’ll not only be comfortable reusing other people’s crate, but be a valuable contributor to the wider Rust community.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;So this summarized the highlights of this course. If you’ve already learned the basics of Rust and would like to take your Rust skill to the next level, please check this course out. You can find this course on the following platforms: &lt;a href=&quot;https://www.udemy.com/building-reusable-code-with-rust/&quot;&gt;Udemy&lt;/a&gt;, &lt;a href=&quot;https://www.oreilly.com/library/view/building-reusable-code/9781788399524/&quot;&gt;Safari Books&lt;/a&gt; and &lt;a href=&quot;https://www.packtpub.com/application-development/building-reusable-code-rust-video&quot;&gt;Packt&lt;/a&gt;.&lt;/p&gt;

</description>
        <pubDate>Fri, 16 Nov 2018 21:12:18 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/11/16/new-rust-course-building-reuseable-code-with-rust.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/11/16/new-rust-course-building-reuseable-code-with-rust.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Chatting with your website visitors through Chatra</title>
        <description>&lt;p&gt;&lt;strong&gt;Update (May 31, 2025):&lt;/strong&gt; &lt;em&gt;I’ve removed the Chatra chat widget from my website today. While it was a useful tool, I found that many users mistakenly thought it was AI-powered and would be quite rude when interacting with what they assumed was a chatbot. If you need to reach out, please feel free to contact me via email listed at the bottom of this website instead.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;When I started the blog, I didn’t add a message board below each article because I don’t have the time to deal with spam. Due to &lt;a href=&quot;https://en.wikipedia.org/wiki/Broken_windows_theory&quot;&gt;broken windows theory&lt;/a&gt;, if I leave the spam unattended my blog will soon become a landfill for spammers. But nowadays many e-commerce site or brand sites have a live chatting box, which will solve my problem because I can simply ignore spam, while interested readers can ask questions and provide feedbacks easily. That’s why when my sponsor, &lt;a href=&quot;https://chatra.io/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;Chatra.io&lt;/a&gt;, approached me with their great tool, I fell in love with it right away and must share it with everyone.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;how-it-works&quot;&gt;How it works&lt;/h1&gt;
&lt;p&gt;First, signup for a free account &lt;a href=&quot;https://app.chatra.io/?enroll=&amp;amp;partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;here&lt;/a&gt;, and you’ll be logged into a clean and modern chat interface.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/chatra/chat_page.png&quot; alt=&quot;first login page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You’ll get a JavaScript widget snippet (in the “Set up &amp;amp; customize” page or email), which you can easily place onto your site (even if you don’t have a backend, like this site). A chat button will immediately appear on your site. Your visitor can now send you messages, and you can choose to reply them right away or followup later using their &lt;a href=&quot;https://app.chatra.io/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;web dashboard&lt;/a&gt;, &lt;a href=&quot;https://chatra.io/apps/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;desktop&lt;/a&gt; or &lt;a href=&quot;https://chatra.io/apps/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;mobile app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/chatra/chat_box.png&quot; alt=&quot;chat box&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;what-i-love-about-chatra&quot;&gt;What I love about Chatra&lt;/h1&gt;

&lt;h3 id=&quot;easy-setup-and-clean-ui&quot;&gt;Easy setup and clean UI&lt;/h3&gt;
&lt;p&gt;As you can see, the setup is simply pasting a block of code into your blog template (or use their app or plugin for &lt;a href=&quot;https://chatra.io/help/cms/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;your platform&lt;/a&gt;), and it works right away. The chat interface is modern and clean, you can “get it” within no time if you ever used any chat app.&lt;/p&gt;

&lt;h3 id=&quot;considerations-for-bloggers-who-cant-be-online-all-day&quot;&gt;Considerations for bloggers who can’t be online all day&lt;/h3&gt;
&lt;p&gt;You might wonder, “I don’t have an army of customer service agents, how can I keep everyone happy with only myself replying messages?”. But Chatra already considered that for you with &lt;a href=&quot;https://chatra.io/messenger/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;messenger mode&lt;/a&gt;, which can receive messages 24/7 even if you are offline. A bot will automatically reply to your visitor and ask for their contact details, so you can follow up later with an email. Every live or missed message can be configured to be sent to your email, so you can check them in batch after a while. Also messaging history are preserved even if the visitor left and come back later, so you get the context of what they were saying. It’s also important to to set expectations for your visitor, to let them know you are working alone and can’t reply super fast. That brings us to the next point: customizable welcome messages and prompts.&lt;/p&gt;

&lt;h3 id=&quot;customizable-and-programmable&quot;&gt;Customizable and programmable&lt;/h3&gt;
&lt;p&gt;Almost everything in Chatra is customizable. From the welcome message, chat button text, to the automatic reply content. So instead of saying “We are a big team and we’ll reply in 10 mins, guaranteed!”, you can instead say something along the line of “Hi, I’m running this site alone and I’d love to hear from you.  I’ll get back to you within days”. Besides customizing the look and feel and tone of speech, you can also setup triggers that automatically initiate a chat when criteria meet. For example we can send a automated message when a visitor reads the article for more then 1 minute. Of course you can further customize the experience using the &lt;a href=&quot;https://chatra.io/help/api/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;developer API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/chatra/trigger.png&quot; alt=&quot;trigger setup page&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;out-of-the-box-google-analytics-integration&quot;&gt;Out-of-the-box Google Analytics integration&lt;/h3&gt;
&lt;p&gt;One thing I really care about is understanding how my visitors interact with the site, and how I can optimize the content and UX to further engage them. I did that through Google Analytic. Much to my amazement, Chatra detected my Google analytics configuration and automatically send relevant events to my &lt;a href=&quot;https://chatra.io/help/analytics/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;Google Analytic tracking&lt;/a&gt;, without me even setting up anything. I can directly create &lt;a href=&quot;https://support.google.com/analytics/answer/1012040&quot;&gt;goals&lt;/a&gt; based on the events and track the conversion funnel leading to a chat.&lt;/p&gt;

&lt;h1 id=&quot;pricing-and-features&quot;&gt;Pricing and features&lt;/h1&gt;
&lt;p&gt;Chatra has a &lt;a href=&quot;https://chatra.io/plans/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;free&lt;/a&gt; and a &lt;a href=&quot;https://chatra.io/plans/?partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;paid plan&lt;/a&gt;, and also a 2-week trial period that allows you to test everything before paying. The number of registered agents, connected websites and concurrent chats is unlimited in all plans. 
The free plan has basic features, including mobile apps, Google Analytics integration, some API options, etc., and allows 1 agent to be online at a time, which is sufficient enough for a one-person website like mine. But you can have several agents taking turns chatting: when one goes offline, another connects. And even if the online spot is already taken, other agents can still access the dashboard and read chats.&lt;/p&gt;

&lt;p&gt;The paid plan starts at $15 per month and gives you access to all features, including automatic triggers and visitors online list, saved replies, typing insights, visitor information, integration with services like Zapier, Slack, Help Scout and more, and allows as many agents online as paid for. Agents on the paid plan can also take turns chatting, so there’s no need to pay for all of them.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;All in all, Chatra is a nice tool to further engage your visitors. The free plan is generous enough for most small scale websites. In case you scales up in the future, their paid plan is affordable and pays for itself after a few successful sales. So if you want an easy and convenient way to chat with your visitors, gain feedback and have more insights into your users, you should give Chatra a try with this &lt;a href=&quot;https://app.chatra.io/?enroll=&amp;amp;partnerId=3Leg7HgLErPj4Fy6v&quot;&gt;link&lt;/a&gt; now.&lt;/p&gt;

</description>
        <pubDate>Mon, 13 Aug 2018 18:59:29 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/08/13/chatting-with-your-website-visitors-through-chatra.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/08/13/chatting-with-your-website-visitors-through-chatra.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>How to Unit Test WebExtensions</title>
        <description>&lt;p&gt;We all know that unit-testing is a good software engineering practice, but sometimes the hassle of setting up the testing environment will keep us from doing it in the first place. After Firefox 57, WebExtension has become the new standard for writing add-ons for Firefox. How do you set up everything to start testing your WebExtension-based add-ons?&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;In the earlier format of the Firefox add-ons, namely the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Archive/Add-ons/Add-on_SDK&quot;&gt;Add-on SDK&lt;/a&gt; (a.k.a. Jetpack), there is a built-in command for unit-test (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Archive/Add-ons/Add-on_SDK/Tools/jpm#jpm_test&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jpm test&lt;/code&gt;&lt;/a&gt;). But for WebExtension, as far as I know, doesn’t have such thing built in. Luckily all the technology used in WebExtension is still standard web technology, so we can use off-the-shelf JavaScript unit-testing frameworks.&lt;/p&gt;

&lt;p&gt;I want to keep my tests as simple as possible, so I made some assumptions:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;I don’t test WebExtension API calls. I keep a thin layer of wrapper around WebExtension API calls, and I don’t put too much logic into them. So hopefully the risk is low enough to not test. Anything more complex like the business logic or custom data structures or functions are all tested.&lt;/li&gt;
  &lt;li&gt;I don’t like to use non-standard module systems. As far as I know WebExtension doesn’t support ES6 module yet. So I follow the good old way of including all the JavaScript I need in the page (or as a background page).&lt;/li&gt;
  &lt;li&gt;I don’t use Node.js libraries in add-ons, period.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;mocha-and-expectjs&quot;&gt;Mocha and expect.js&lt;/h2&gt;
&lt;p&gt;We will be using &lt;a href=&quot;https://mochajs.org/#running-mocha-in-the-browser&quot;&gt;Mocha&lt;/a&gt; test framework and &lt;a href=&quot;https://github.com/Automattic/expect.js&quot;&gt;expect.js&lt;/a&gt; assertion library, but you can use any test framework that supports running in browsers.
We’ll be using the browser version of Mocha. You need to create an HTML file like this:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Unit&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Tests&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Mocha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/title&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stylesheet&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/head&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/div&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;bdd&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;calculator.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;test.calculator.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;checkLeaks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/script&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/body&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/html&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the file you can see that we imported the Mocha library and expect.js library from CDN, so we don’t need to install anything locally. We’ll be testing a imaginary calculator library used in our extension. The test cases are written in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.calculator.js&lt;/code&gt; file. The classes and functions under tested are placed in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculator.js&lt;/code&gt; file. We load the module under test and the test case in the file as well&lt;/p&gt;

&lt;p&gt;The way to run it is to simply open this file in a Firefox, if everything goes well you should see the following screen:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/unittest_webext/empty_test.png&quot; alt=&quot;empty test&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I usually put the main logic and code that interacts with extension APIs in a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt;. Any other business and utility functions goes into separate JS files, which are test using the above unit testing framework. They are all load together with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;background.js&lt;/code&gt; as background scripts. To do so, you need to add all of them to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest.json&lt;/code&gt; like so:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Calculator Add-on&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;background&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;background.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;calculator.js&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can write more tests like so in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.calculator.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;My calculator&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;can add 1 and 1 and get 2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;my_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// my_add is definied in calculator.js&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run the page again, and you should see it failing, because you haven’t defined &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_add&lt;/code&gt; yet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/unittest_webext/my_add_not_defined.png&quot; alt=&quot;my_add not defined error&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, write your my_add in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculator.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;my_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run the page once again, and your test now passes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/unittest_webext/my_add_pass.png&quot; alt=&quot;my_add passed&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;testing-asynchronous-code&quot;&gt;Testing asynchronous code&lt;/h2&gt;
&lt;p&gt;Many WebExtension APIs return &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;promises&lt;/a&gt; so you can write functions that receives and returns promises so you can chain them. The problem is that if you have and error in the Promise chain, the error will be consumed by the promise, so the test framework will not catch it, resulting in an always-passing test. Let’s say you want to write a function that counts how many times you’ve visited Facebook, you can use the &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/history/getVisits&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;browser.hisotry.getVisits()&lt;/code&gt;&lt;/a&gt; API, which returns a promise. So first we write a test for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count_visits()&lt;/code&gt; function we are about to write.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Facebook counter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;can count&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mock_getVisits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;c1&quot;&gt;// Simulate the getVisits API that returns 3 results&lt;/span&gt;
       &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]);&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

     &lt;span class=&quot;nx&quot;&gt;mock_getVisits&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count_visits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
         &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we implement the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count_visits()&lt;/code&gt;, but we made a typo by writing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;length&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;legnht&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;count_visits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;history_items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;history_items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;legnht&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case, the function will always return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;undefined&lt;/code&gt;, because there is no such thing as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;legnht&lt;/code&gt; for arrays. But if you run the test, you’ll find the test still passing. If you open the developer, you’ll see the actual error. But seems the test framework didn’t catch it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/unittest_webext/not_catched.png&quot; alt=&quot;async code not catched&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The latest Mocha library already have built in promise support, you only need to make sure you return the promise so the error can be captured.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Facebook counter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;can count&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mock_getVisits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;c1&quot;&gt;// Simulate the getVisits API that returns 3 results&lt;/span&gt;
         &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]);&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;//   vvvvvv Notice the &quot;return&quot; here&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mock_getVisits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count_visits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
         &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now the test fails as expected:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/unittest_webext/catched.png&quot; alt=&quot;async code catched&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Another way is to use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function&quot;&gt;async/await&lt;/a&gt; to make your async function looks sync. Mocha can easily handle that as usual synchronous functions.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Facebook counter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Notice the async here             vvvvv&lt;/span&gt;
   &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;can count using async/await&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mock_getVisits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;c1&quot;&gt;// Simulate the getVisits API that returns 3 results&lt;/span&gt;
         &lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]);&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// and await here vvvvv&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;visits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mock_getVisits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
     &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count_visits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Writing test WebExtension it’s not as hard as you might think. Simply copying and pasting the HTML and you’re ready to go. You don’t need to install anything or set up complex Node.js compiling pipeline. Start testing your WebExtension code now, it saved me many hours of debugging time, and I believe it will help you as well.&lt;/p&gt;

</description>
        <pubDate>Sun, 24 Jun 2018 19:44:13 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/06/24/how-to-unit-test-webextensions.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/06/24/how-to-unit-test-webextensions.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>如何貢獻開源專案？</title>
        <description>&lt;p&gt;貢獻開源專案是一個提高自己技能的好機會，同時也能提昇自己在軟體界的能見度與溝通技巧。以下是我貢獻 Mozilla 的 &lt;a href=&quot;https://servo.org/&quot;&gt;Servo&lt;/a&gt; 瀏覽器引擎的一些心得，提供想要一探開源的新手一些方向。&lt;/p&gt;

&lt;!--more--&gt;
&lt;h1 id=&quot;心理準備&quot;&gt;心理準備&lt;/h1&gt;
&lt;p&gt;首先你要問自己為什麼你想要貢獻開源？是為了提高軟體技術？為了充實履歷表？畢竟能做的專案太多，更別說自己開新專案，所以必須先想清楚自己的初衷，然後找到一個適合自己的需求又能有所貢獻的專案。 要找到適合的專案，可以優先從以下方向開始看：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;自己正在使用的開源工具/框架/程式語言&lt;/li&gt;
  &lt;li&gt;自己想學習的新開源技術&lt;/li&gt;
  &lt;li&gt;自己非常需要的工具，自己開新專案&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;專案首先一定要對自己有用，否則很難有動力繼續做下去。寫軟體的很大一個動力來自於巧妙的利用程式解決了自己的問題的成就感。如果這個成果又能被其他人使用的話，就是一個更強烈的正向循環。&lt;/p&gt;

&lt;p&gt;另外一個常見的錯誤是因為覺得自己技術底子不夠，所以把所有時間花在讀書而沒有動手實做。任何一個程式語言的書絕對都夠你讀個三年五年，更別說每個專案有其背景知識，例如 Web 技術、瀏覽器技術、編譯器、作業系統，即使是大學本科花四年也只能學到皮毛，所以所謂「我先把該學的學完就可以開始貢獻了」絕對是一個癡人說夢的想法。最好的方法是開始從簡單的 bug 下手，例如修一些 typo 或是非常簡單的錯誤，一方面可以練習整個專案的開發流程，也可以開始與專案的其他貢獻者互動。假設程式本身真的太難，也有一些比較簡單的開始方式，例如幫忙撰寫或翻譯技術文件、主辦讀書會、寫部落格分享自己的學習心得、回報軟體的錯誤等等。這些方法可以一邊學習專案本身的技術，同時又開始在社群中培養一些能見度。&lt;/p&gt;

&lt;p&gt;最後，開源專案是由人組成的，因此溝通（以及誤解）是逃避不了的。很多時候活躍的專案因為開發速度太快，很少會有完整的文件，很多時候一些架構設計、know-how 可能都在幾個核心成員的腦中。因此當你盡了自己所能讀完了文件與原始碼，還是無法獲得解答的時候，最快的方法就是去專案的聊天室或討論區裡面發問。很多時候你花了幾天讀不懂的原始碼可能只是因為某些歷史原因而這麼複雜，只要簡單一問就能夠馬上進步。不同社群之間的溝通模式差異很大，有些社群內可能核心成員真的很忙，或是比較精英主義，對於新手就會比較冷漠。這時候也只能盡量保持客氣，一般來說只要你的貢獻對社群真的有用，隨著時間過去總是會慢慢被接納。若是社群成員真的太惡意，你大可以找其他的專案貢獻。&lt;/p&gt;

&lt;p&gt;以下將以我 2014 年開始貢獻的 Servo 專案作為例子，跟大家介紹一下如何一步一步的開始貢獻。&lt;/p&gt;

&lt;h1 id=&quot;基礎知識&quot;&gt;基礎知識&lt;/h1&gt;
&lt;p&gt;以下有些基礎技能你最好先學會（或是先掃過一下教學文件然後邊做邊學）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;git: 幾乎所有開源專案都有某種版本控制系統，而 git 是最常見的一種。
    &lt;ul&gt;
      &lt;li&gt;感謝 &lt;a href=&quot;http://denny.one&quot;&gt;Denny&lt;/a&gt; 的 &lt;a href=&quot;http://denny.one/git-slide&quot;&gt;教學投影片&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;感謝 &lt;a href=&quot;https://yodalee.blogspot.tw/?m=1&quot;&gt;葉闆&lt;/a&gt; 的&lt;a href=&quot;https://yodalee.blogspot.tw/2017/12/git-video.html?m=1&quot;&gt;教學影片&lt;/a&gt;以及&lt;a href=&quot;https://yodalee.blogspot.tw/2014/05/pull-requestgithub.html?m=1&quot;&gt;Pull request 介紹&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;GitHub: GitHub 是一個 git 的託管服務，同時他也提供一些協作的功能例如 pull request， issues。熟悉 GitHub 可以幫助你找到數以千計的開源專案，並且快速的加入開發。
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;http://www.ithome.com.tw/news/95283&quot;&gt;基本觀念（中文）&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://guides.github.com/activities/hello-world/&quot;&gt;基本流程（英文）&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://guides.github.com/activities/forking/&quot;&gt;Fork（英文）&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;找尋可以貢獻的-bug&quot;&gt;找尋可以貢獻的 bug&lt;/h1&gt;
&lt;p&gt;Servo 的 bug (程式缺陷) 或是新功能請求都在 GitHub 上管理，可以至 Servo 的 &lt;a href=&quot;https://github.com/servo/servo/issues&quot;&gt;Issues 頁面&lt;/a&gt;瀏覽。 適合新手的 bug 都蒐集在 &lt;a href=&quot;https://starters.servo.org&quot;&gt;Servo Starters&lt;/a&gt; 頁面上，也可以至 Servo Issues 直接查詢&lt;a href=&quot;https://github.com/servo/servo/issues?utf8=✓&amp;amp;q=is%3Aopen%20is%3Aissue%20label%3AE-easy%20-label%3AC-assigned&quot;&gt;有 E-easy 標籤的 bug&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/open_source/servo_starters.png&quot; alt=&quot;servo starters&quot; /&gt;&lt;/p&gt;

&lt;p&gt;找到 issue 以後，試著理解問題是什麼，如果下面留言中沒有人表示想要做，就可以立刻留言表示興趣，並且向開 bug 的人請求進一步的指示或協助。以下是一個範例的回應：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hi @&lt;username&gt;, I&apos;m interested in this issue.&lt;/username&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;想要知道相關原始碼在哪裡 =&amp;gt; Could you point me to the relevant code/file?&lt;/li&gt;
  &lt;li&gt;想要知道如何重現出這個問題 =&amp;gt; Could you tell me the exact step to reproduce this issue?&lt;/li&gt;
  &lt;li&gt;想要知道相關的標準規範 (W3C, WHATWG, etc. ) =&amp;gt; Could point me to the relevant spec?&lt;/li&gt;
  &lt;li&gt;看不太懂也不知道要問什麼，希望對方進一步說明 =&amp;gt; Could you give me more information on how this thing works and how I should proceed?&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;如何下載修改並測試-servo-的原始碼&quot;&gt;如何下載、修改並測試 Servo 的原始碼&lt;/h1&gt;
&lt;p&gt;要取得 Servo 的程式碼開始修改，首先要從 Servo 官方的 repository “fork” 出一版，按下右上方的 “Fork” 按鈕，你的帳號下就會出現一個 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servo&lt;/code&gt; repository。(詳見&lt;a href=&quot;https://git-scm.com/book/zh-tw/v2/GitHub-%E5%8F%83%E8%88%87%E4%B8%80%E5%80%8B%E5%B0%88%E6%A1%88&quot;&gt;Forking&lt;/a&gt;)。需要這麼做的原因是因為一般使用者沒有權限直接修改 servo/servo 的原始碼，所以你必須複製(“fork”)一份自己的版本，修改完以後在把改動提交給官方審核 (也就是所謂的 Pull Request，也可以參考這篇&lt;a href=&quot;https://yodalee.blogspot.tw/2014/05/pull-requestgithub.html?m=1&quot;&gt;文章&lt;/a&gt;)。一旦官方對你的 pull request 滿意的話，就會被 merge 到 master，你修改的部份就正式進到 servo 裡面了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/open_source/fork.png&quot; alt=&quot;fork button&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接下來你就可以從你剛剛 fork 出來的 servo repository 按下 “Clone or download”，取得 clone 用的網址。接下來開啟一個終端機，輸入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone git@github.com/&amp;lt;你的帳號&amp;gt;/servo.git&lt;/code&gt; (把&lt;你的帳號&gt;代換成你的 clone 網址)。這個下載需要耗費不少時間，記得確保你的網路暢通，就可以去泡杯咖啡慢慢等候了。&lt;/你的帳號&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/open_source/forked.png&quot; alt=&quot;forked&quot; /&gt;&lt;/p&gt;

&lt;p&gt;等下載完畢，首先要做的就是確保你的環境能夠正確的編譯並執行 servo。Servo 的文件中有寫出各個平台所需要事先安裝的一些套件(看&lt;a href=&quot;https://github.com/servo/servo#setting-up-your-environment&quot;&gt;這裡&lt;/a&gt;) ， 例如 Ubuntu 就需要安裝&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt install git curl freeglut3-dev autoconf libx11-dev \
       libfreetype6-dev libgl1-mesa-dri libglib2.0-dev xorg-dev \
       gperf g++ build-essential cmake virtualenv python-pip \
       libssl-dev libbz2-dev libosmesa6-dev libxmu6 libxmu-dev \
       libglu1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然後執行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach build --dev&lt;/code&gt; 來試著編譯。注意除了 OSX 與 Linux 外，其他平台的狀況都不是很穩定，所以如果無法順利編譯也不要氣餒，記得到 &lt;a href=&quot;https://github.com/servo/servo/issues&quot;&gt;issues&lt;/a&gt; 看看有沒有其他人也遇到一樣的問題，或是開個新 issue 尋求協助 （記得附上你遇到的錯誤訊息）。&lt;/p&gt;

&lt;p&gt;一但編譯順利，你就可以跑 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach run&lt;/code&gt; 來測試 servo 是否正常運作，你應該會看到 servo 開啟一個視窗，並且載入 servo 的官方網站。你也執行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach run -b&lt;/code&gt; 試試看一個用 HTML 打造的簡易瀏覽器界面 &lt;a href=&quot;https://github.com/browserhtml/browserhtml&quot;&gt;browser.html&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/open_source/browserhtml.gif&quot; alt=&quot;browserhtml&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果你編譯一切順利，恭喜！你可以開始動工了。通常我們會為每一個 issue 或是功能開一個新的 branch，以方便切換。當你完成更改以後，記得 commit 並且寫上清楚的 commit 訊息。一般來說好的 commit message 可以寫成：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Implemented &amp;lt;a JavaScript API, CSS property or other feature&amp;gt; (實作了&amp;lt;一個 JavaScript API、CSS 屬性或其他功能&amp;gt;)，例如 Implemented window.open(), 或是 Implemented CSS grid&lt;/li&gt;
  &lt;li&gt;Modified/Fixed/Changed &lt;some part=&quot;&quot; of=&quot;&quot; the=&quot;&quot; code=&quot;&quot;&gt; to &lt;provide some=&quot;&quot; benefit=&quot;&quot;&gt; (修改/修正/改動了&lt;某個部份&gt;來&lt;提供某些好處&gt;)， 例如 Change the internal implementation of class Foo to enhance readabiltiy。&lt;/提供某些好處&gt;&lt;/某個部份&gt;&lt;/provide&gt;&lt;/some&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;舉例來說，假設我們要實作 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.open()&lt;/code&gt; 這個 JavaScript API， 我們可以&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git branch -b window-open # 開啟一個叫 window-open 的 branch
git checkout window-open
# 實作 window open
./mach build -d  # 確定編譯會過
git commit -m &quot;Implemented window.open()&quot;
git push -u origin window-open # 把 window-open branch 上的東西推到你的 servo repo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;如何提交-pull-request&quot;&gt;如何提交 Pull Request&lt;/h1&gt;
&lt;p&gt;當你的更改完成，記得先檢查以下幾點，以免浪費其他人的時間：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach build -d&lt;/code&gt; 可以順利編譯。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach test-tidy&lt;/code&gt; 會檢查程式風格的問題，例如多餘的空格、錯誤的括號位置、行長度過長之類的問題。&lt;/li&gt;
  &lt;li&gt;相關的測試有通過（不知道要跑什麼測試的話，請到原始的 issue 中問）:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach test-unit&lt;/code&gt; 單元測試&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach test-wpt&lt;/code&gt; web platform test， 主要測試 JS/CSS 相關功能是否符合標準規範&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果都通過就可以到 servo/servo，選 Pull requests 然後按 New pull request。接下來按 compare across forks，然後head fork 選擇 &lt;你的帳號&gt;/servo， compare 選擇你要提交的 branch。簡單檢查一下下面出現的程式碼，如果沒有問題就可以按 Create pull request。&lt;/你的帳號&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/open_source/openpr.png&quot; alt=&quot;open pr&quot; /&gt;&lt;/p&gt;

&lt;p&gt;當你一開 pull request，servo 的 highfive 聊天機器人就會自動跳出來跟你打招呼，並且幫你指定一個 reviewer。所有提交到 servo 的程式碼都需要通過他人審核，這也是一般 open source 專案的慣例，不過 servo 的 reviewer 都相當友善，所以不用太擔心。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/open_source/highfive.png&quot; alt=&quot;highfive&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接下來 reviewer 通常會提供你一些修改的建議，這對很多人來說很難為情，因為要把自己寫的程式攤在日光下給人家批評。不過記得 reviewer 都是對事不對人，所以不要把這些評論當作對自己的人身攻擊。這也是一些非常好的學習機會，通常都可以從 reviewer 那邊學到相當 多 servo 內部的知識以及程式風格的建議。&lt;/p&gt;

&lt;p&gt;如果要根據 reviewer 的意見修正，只要繼續在自己的那份 servo 上修改、commit、並且 git push 到你自己原來的那個 branch 即可，過給秒鐘後再去重新整理 pull request 的頁面就可以發現已經更新到你新推的程式碼了。經過這樣幾次來回修改直到 reviewer 滿意以後，他可能會做幾件事情：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;請你 rebase 或 squash，這些操作比較進階，也超出本篇的篇幅，詳細可以參考&lt;a href=&quot;https://shinglyu.github.io/web/2016/11/08/servo-rebase-and-squash-guide.html&quot;&gt;這篇手把手教學&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;他向另外一隻聊天機器人 @bors-servo 說 “@bors-servo try”，這表示他請 bors-servo 啟動一系列自動測試，但是並不直接 merge 你的 pull request。最後如果測試都通過通常就會進到下一條。&lt;/li&gt;
  &lt;li&gt;他向 bors-servo 說 “@bors-servo r+”，這時候 bors-servo 一樣會先執行一系列測試，如果測試都通過就會直接 merge 你的 pull request。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/open_source/r+.png&quot; alt=&quot;r+&quot; /&gt;&lt;/p&gt;

&lt;p&gt;假設有任何測試沒通過，請不要驚慌，根據測試失敗的原因繼續修改你的程式即可。如果看不懂為何會錯也可以直接在 pull request 裡面發問。&lt;/p&gt;

&lt;p&gt;以上流程可以看一個例子：&lt;a href=&quot;https://github.com/servo/servo/pull/20610&quot;&gt;style: Simplify border-image-repeat serialization&lt;/a&gt;，從中你可以看到各種機器人如何與 reviewer 合作進行審查與測試。 以上就是完整的從找到問題到成功 merge 一個 pull request 的流程，但是在這過程中有許許多多的小環節都有可能出錯，這時千萬記得隨時發問。&lt;/p&gt;

&lt;h1 id=&quot;哪裡查資料與問問題&quot;&gt;哪裡查資料與問問題&lt;/h1&gt;

&lt;p&gt;Servo 是一個非常複雜而且龐大的專案，而且瀏覽器引擎相關的資料在網路上相當稀少。所以以下我蒐集了一些資源可以幫助你更了解相關的背景知識：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/&quot;&gt;How browsers work&lt;/a&gt;: 這是目前網路上最完整最全面的瀏覽器引擎介紹，雖然沒有直接介紹 Servo，但是可以幫助你了解瀏覽器引擎大致是如何運作的。&lt;/li&gt;
  &lt;li&gt;Matt’s &lt;a href=&quot;https://limpet.net/mbrubeck/2014/08/08/toy-layout-engine-1.html&quot;&gt;Let’s build a browser engine&lt;/a&gt;: Matt Brubeck (也是 Servo 的核心成員) 用實際的 code 介紹如何寫出真正可以運作的瀏覽器引擎。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/servo/servo/wiki&quot;&gt;Servo Wiki&lt;/a&gt;: Servo 官方的 wiki，有一些設計的筆記與投影片，不過似乎有一陣子沒更新，記得要檢查一下是不是太舊。&lt;/li&gt;
  &lt;li&gt;Servo code 裡面的註解與文件: Servo 的程式碼中夾雜了不少註解與文件， 假設你在修改某個檔案，記得在同一個資料夾或上下一兩層資料夾找找文件。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://html.spec.whatwg.org/&quot;&gt;WHATWG Spec&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://www.w3.org/Style/CSS/current-work&quot;&gt;W3C Spec&lt;/a&gt;: 假設你處理的是 HTML， JavaScript 或是 CSS 的功能，免不了需要查詢一下官方的規範。
&lt;img src=&quot;/blog_assets/open_source/whatwg.png&quot; alt=&quot;whatwg&quot; /&gt;
&lt;img src=&quot;/blog_assets/open_source/w3c.png&quot; alt=&quot;w3c&quot; /&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-TW/&quot;&gt;MDN&lt;/a&gt;: 官方規範太硬太難唸，想要看一下實際上如何使用可以去 MDN 上搜尋。
&lt;img src=&quot;/blog_assets/open_source/mdn.png&quot; alt=&quot;mdn&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Servo 的主要成員會定期的巡視 GitHub 上的 issue 與 pull request，所以有 issue 相關的問題可以直接在 GitHub 上發問。另外也可以到 &lt;a href=&quot;http://chat.mibbit.com/?server=irc.mozilla.org&amp;amp;channel=%23servo&quot;&gt;#servo IRC&lt;/a&gt; 上直接問問題，通常可以更快得到答案。&lt;/p&gt;

&lt;h1 id=&quot;其他人的貢獻經驗&quot;&gt;其他人的貢獻經驗&lt;/h1&gt;
&lt;p&gt;網路上也有不少人分享他們貢獻 Servo 的經驗，例如來自台灣的劉安齊在 Medium 上分享過「&lt;a href=&quot;https://medium.com/@tigercosmos/%E8%B8%8F%E5%85%A5-mozilla-servo-%E5%85%A9%E5%80%8B%E6%9C%88%E7%9A%84%E5%BF%83%E5%BE%97-9eaf41e021f9&quot;&gt;踏入 Mozilla Servo 兩個月的心得&lt;/a&gt;」。也可以看看 Fausto NA 的 “&lt;a href=&quot;https://brainlessdeveloper.com/2017/08/12/my-experience-contributing-to-servo/&quot;&gt;My experience contributing to Servo&lt;/a&gt;“。 開源是一趟冒險，每個人的體驗都會不同，這裡只能介紹一些比較技術性的面向。希望各位都能在這個過程中學到自己想學的，同時又能對社群有所貢獻。&lt;/p&gt;

</description>
        <pubDate>Sat, 12 May 2018 15:55:42 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/05/12/how-to-contribute-to-open-source.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/05/12/how-to-contribute-to-open-source.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Merge Pull Requests without Merge Commits</title>
        <description>&lt;p&gt;By default, GitHub’s pull request (or GitLab’s merge request) will merge with a merge commit. That means your feature branch will be merged into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; by creating a new commit, and both the feature and master branch will be kept. 
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Let’s illustrate with an example:&lt;/p&gt;

&lt;p&gt;Let’s assume we branch out a feature branch called “new-feature” from the master branch, and pushed a commit called “Finished my new feature”. At the same time someone pushed another commit called “Other’s feature” onto the master branch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/ff-merge/branching.png&quot; alt=&quot;branch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If we now create a pull request for our branch, and get merged, we’ll see a new merge commit called “Merge branch ‘new-feature’”&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/ff-merge/merge_commit.png&quot; alt=&quot;merge_commit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you look at GitHub’s commit history, you’ll notice that the UI shows a linear history, and the commits are ordered by the time they were pushed. So if multiple people merged multiple branches, all of them will be mixed up. The commits on your branch might interlace with other people’s commits. More importantly, some development teams don’t use pull request or merge requests at all. Everyone is suppose to push directly to master, and maintain a linear history. How can you develop in branches but merge them back to master without a merge commit?&lt;/p&gt;

&lt;p&gt;Under the hood, GitHub and GitLab’s “merge” button uses &lt;a href=&quot;https://git-scm.com/docs/git-merge#_fast_forward_merge&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff&lt;/code&gt; option&lt;/a&gt;, which will force create a merge commit. What you are looking for is the opposite: &lt;a href=&quot;https://git-scm.com/docs/git-merge#_fast_forward_merge&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ff-only&lt;/code&gt;&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ff&lt;/code&gt; stands for &lt;a href=&quot;https://git-scm.com/docs/git-merge#_fast_forward_merge&quot;&gt;fast-forward&lt;/a&gt;). This option will cleanly append your commits to master, without creating a merge commit. But it only works if there is not new commits in master but not in your feature branch, otherwise it will fail with a warning. So if someone pushes to master and you did a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull&lt;/code&gt; on your local master, you need to do a rebase on your feature branch before using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ff-only&lt;/code&gt; merge. Let’s see how to do this with an example:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout new-feature &lt;span class=&quot;c&quot;&gt;# Go to the feature branch named &quot;new-feature&quot;&lt;/span&gt;
git rebase master
&lt;span class=&quot;c&quot;&gt;# Now your feature have all the commits from master&lt;/span&gt;
git checkout master &lt;span class=&quot;c&quot;&gt;#Go back to master&lt;/span&gt;
git merge &lt;span class=&quot;nt&quot;&gt;--ff-only&lt;/span&gt; new-feature
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After these commands, your master branch should contain the commits from the feature branch, as if they are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cherry-pick&lt;/code&gt;ed from the feature branch. You can then push directly remote.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If unfortunately someone pushed more code to the remote master while you are doing this, your push might fail. You can pull, rebase and push again like so:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git pull &lt;span class=&quot;nt&quot;&gt;--rebase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://help.github.com/articles/about-pull-request-merges/&quot;&gt;GitHub’s documentation&lt;/a&gt; has some nice illustrations about the two different kind of merges.&lt;/p&gt;

&lt;p&gt;Here is a script that does the above for you. To run it you have to checkout to the feature branch you want to merge back to master, then execute it. It will also pull and rebase both your feature and master branch to the most up-to-date remote master during the operation.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/usr/bin/env bash
CURRBRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ $CURRBRANCH = &quot;master&quot; ]
then
    echo &quot;Already on master, aborting...&quot;
    exit 1 
fi

echo &quot;Merging the change from $CURRBRANCH to master...&quot;
echo &quot;Rebasing branch $CURRBRANCH to latest master&quot;
git fetch origin master &amp;amp;&amp;amp; \
git rebase origin/master &amp;amp;&amp;amp; \
echo &quot;Checking out to master and pull&quot; &amp;amp;&amp;amp; \
git checkout master &amp;amp;&amp;amp; \
git rebase origin/master &amp;amp;&amp;amp; \
echo &quot;Merging the change from $CURRBRANCH to master...&quot; &amp;amp;&amp;amp; \
git merge --ff-only $CURRBRANCH &amp;amp;&amp;amp; \
git log | less 
echo &quot;DONE. You may want to do one last test before pushing&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s worth mentioning that both GitHub and GitLab allows you to do the fast-forward (and squash) merge on it’s UI. But it’s configured on a per-repository basis, so if you don’t control the repository, you might have to ask your development team’s administrator to turn on the feature. You can read more about this feature in &lt;a href=&quot;https://help.github.com/articles/configuring-commit-squashing-for-pull-requests/&quot;&gt;GitHub’s documentation&lt;/a&gt; and &lt;a href=&quot;https://docs.gitlab.com/ee/user/project/merge_requests/fast_forward_merge.html#enabling-fast-forward-merges&quot;&gt;GitLab’s documentation&lt;/a&gt;. If you are interested in squashing the commits manually, but don’t know how, check out my &lt;a href=&quot;https://shinglyu.github.io/web/2016/11/08/servo-rebase-and-squash-guide.html&quot;&gt;previous post about squashing&lt;/a&gt;.&lt;/p&gt;

</description>
        <pubDate>Sun, 25 Mar 2018 21:46:36 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/03/25/merge-pull-requests-without-merge-commits.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/03/25/merge-pull-requests-without-merge-commits.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Minimal React.js Without A Build Step (Updated)</title>
        <description>&lt;p&gt;Back in 2016, I wrote a &lt;a href=&quot;/web/2016/04/06/minimal_react.html&quot;&gt;post&lt;/a&gt; about how to write a React.js page without a build step. If I remember correctly, at that time the official React.js site have very little information about running React.js without [Webpack][webpack], [in-browser Babel transpiler][babel] is not very stable and they are deprecating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSXTransformer.js&lt;/code&gt;. After the post my focus turned to browser backend projects and I haven’t touch React.js for a while. Now after 1.5 years, when I try to update one of [my React.js project][itinerary-viewer], I notice that the official site now has a clearer instruction on how to use React.js without a build step. So I’m going to write an update the post here.&lt;/p&gt;

&lt;p&gt;You can find the example code on &lt;a href=&quot;https://github.com/shinglyu/minimal-react&quot;&gt;GitHub&lt;/a&gt;.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;1-load-reactjs-from-cdn-instead-of-npm&quot;&gt;1. Load React.js from CDN instead of npm&lt;/h2&gt;
&lt;p&gt;You can use the official minimal HTML template &lt;a href=&quot;https://reactjs.org/docs/try-react.html#minimal-html-template&quot;&gt;here&lt;/a&gt;. The most crucial bit is the importing of scripts:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/react@16/umd/react.development.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/react-dom@16/umd/react-dom.development.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/babel-standalone@6.15.0/babel.min.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want better error message, you might want to add the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crossorigin&lt;/code&gt;&lt;/a&gt; attribute to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags, as suggested in the &lt;a href=&quot;https://reactjs.org/docs/cdn-links.html&quot;&gt;official document&lt;/a&gt;. Why the attribute you ask? As describe in &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin&quot;&gt;MDN&lt;/a&gt;, this attribute will allow your page to log errors on CORS scripts loaded from the CDN.&lt;/p&gt;

&lt;p&gt;If you are looking for better performance, load the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.production.min.js&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.development.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;2-get-rid-of-jsx&quot;&gt;2. Get rid of JSX&lt;/h2&gt;
&lt;p&gt;I’m actually not that against JSX now, but If you don’t want to include the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;babel.min.js&lt;/code&gt; script, you can consider using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;React.createElement&lt;/code&gt; function. Actually all JSX elements are syntatic sugar for calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;React.createElement()&lt;/code&gt;.  Here are some examples:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello Word&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;can be written as&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Hello World&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And if you want to pass attributes around,  you can do&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onClick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{this.props.clickHandler}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{this.state.data}&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  Click Me!
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                      &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;clickHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                      &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; 
                    &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Click Me!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course you can have nested elements:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello World&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&amp;gt;&lt;/span&gt;Click Me!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Hello World&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Click Me!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can read how this works in the &lt;a href=&quot;https://reactjs.org/docs/react-without-jsx.html&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Split the React.js code into separate files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the official &lt;a href=&quot;https://reactjs.org/docs/try-react.html#minimal-html-template&quot;&gt;HTML template&lt;/a&gt;, they show how to write script directly in HTML like:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/babel&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

      &lt;span class=&quot;nx&quot;&gt;ReactDOM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,
&lt;/span&gt;        &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But for real-word projects we usually don’t want to throw everything into one big HTML file. So you can put everything between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;/script&amp;gt;&lt;/code&gt; in to a separate JavaScript file, let’s name it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.js&lt;/code&gt; and load it in the original HTML like so:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app.js&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/babel&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The pitfall here is that you must keep the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type=&quot;text/babel&quot;&lt;/code&gt; attribute if you wants to use JSX. Otherwise the js script will fail when it first reaches a JSX tag, resulting an error like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SyntaxError: expected expression, got &apos;&amp;lt;&apos;[Learn More]        app.js:2:2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;using-3rd-party-npm-components&quot;&gt;Using 3rd-party NPM components&lt;/h1&gt;

&lt;h2 id=&quot;modules-with-browser-support&quot;&gt;Modules with browser support&lt;/h2&gt;
&lt;p&gt;You can find tons of ready-made React components on NPM, but the quality varies. Some of them are released with browser support, for example &lt;a href=&quot;https://reactstrap.github.io/&quot;&gt;Reactstrap&lt;/a&gt;, which contains Bootstrap 4 components wrapped in React. In its documentation you can see a “CDN” section with a CDN link, which should just work by adding it to a script tag:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- react-transition-group is required by reactstrap --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/react-transition-group@2.2.1/dist/react-transition-group.min.js&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://cdnjs.cloudflare.com/ajax/libs/reactstrap/4.8.0/reactstrap.min.js&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;then you can find the components in a gloabl variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reactstrap&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/babel&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// &quot;Import&quot; the components from Reactstrap&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Reactstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Render a Reactstrap Button element onto root&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;ReactDOM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Button&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;danger&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;world&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/Button&amp;gt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,
&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(In case you are curious, the first line is the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructurig.&quot;&gt;destructing assignment of objects&lt;/a&gt; in JavaScript).&lt;/p&gt;

&lt;p&gt;Of course it also works without JSX:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// &quot;Import&quot; the components from Reactstrap&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Reactstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Render a Reactstrap Button element onto root&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;ReactDOM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;danger&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello world!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;modules-without-browser-support&quot;&gt;Modules without browser support&lt;/h2&gt;
&lt;p&gt;For modules without explicit browser support, you can still try to expose it to the browser with &lt;a href=&quot;http://browserify.org/&quot;&gt;Browserify&lt;/a&gt;, as described in &lt;a href=&quot;http://krasimirtsonev.com/blog/article/distributing-react-components-babel-browserify-webpack-uglifyjs&quot;&gt;this post&lt;/a&gt;. Browserify is a tool that converts a Node.js module into something a browser can take. There are two tricks here:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--standalone&lt;/code&gt; option so Browserify will expose the component under the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window&lt;/code&gt; namespace, so you don’t need a module system to use it.&lt;/li&gt;
  &lt;li&gt;Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;browserify-global-shim&lt;/code&gt; plugin to strip all the usage of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;React&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReactDOM&lt;/code&gt; in the NPM module code, so it will use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;React&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReactDOM&lt;/code&gt; we included using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ll use a very simple React component on NPM, &lt;a href=&quot;https://www.npmjs.com/package/simple-react-modal&quot;&gt;simple-react-modal&lt;/a&gt;, to illustrate this. First, we download this module to see what it looks like:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;simple-react-modal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules/simple-react-modal&lt;/code&gt;, we can see a pre-built JavaScript package in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt; folder. Now we can install Browserify by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install -g browserify&lt;/code&gt;. But we can’t just run it yet, because the code uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require(&apos;react&apos;)&lt;/code&gt; but we want to use our version loaded in the browser. So we need to install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install browserify-global-shim&lt;/code&gt; and add the configuration to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// package.json&lt;/span&gt;
&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;browserify-global-shim&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;react&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;react-dom&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ReactDOM&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can run&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;browserify node_modules/simple-react-modal &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; simple-react-modal-browser.js &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--transform&lt;/span&gt; browserify-global-shim &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--standalone&lt;/span&gt; Modal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ll get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simple-react-modal-browser.js&lt;/code&gt; file, which we can just load in the browser using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag. Then you can use the Modal like so:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// &quot;Import&quot; the components from Reactstrap&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Modal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Modal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Render a Reactstrap Button element onto root&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;ReactDOM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Modal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;closeOnOuterClick&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; 
      &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; 
      &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;(There are some implementation detail about the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simple-react-modal&lt;/code&gt; module in the above code, so don’t be worried if you don’t get everything.)&lt;/p&gt;

&lt;h1 id=&quot;the-benefits&quot;&gt;The benefits&lt;/h1&gt;

&lt;p&gt;Using this method, you can start prototyping by simply copying a HTML file. You don’t need to install Node.js, NPM and all the NPM modules that quickly make your small proof-of-concept page bloat.&lt;/p&gt;

&lt;p&gt;Secondly, this method is compatible with the &lt;a href=&quot;https://github.com/facebook/react-devtools&quot;&gt;React-DevTools&lt;/a&gt;. Which is available in both Firefox and Chrome. So debugging is much easier.&lt;/p&gt;

&lt;p&gt;Finally, It’s super easy to deploy the program. Simply drop the files into any web server (or use GitHub pages). The server doesn’t even need to run Node and NPM, any pure HTTP server will be sufficient. Other people can also easily download the HTML file and start hacking. This is a very nice way to rapidly prototype complex UIs without spending an extra hour setting up all the build steps (and maybe waste another 2 hour helping the team setting their environment).&lt;/p&gt;

</description>
        <pubDate>Thu, 08 Feb 2018 21:39:06 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/02/08/minimal-react-js-without-a-build-step-updated.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/02/08/minimal-react-js-without-a-build-step-updated.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Taking notes with MkDocs</title>
        <description>&lt;p&gt;I’ve been using &lt;a href=&quot;https://tiddlywiki.com/&quot;&gt;TiddlyWiki&lt;/a&gt; for note-taking for a few years. I use them to keep track of my technical notes and checklists. &lt;a href=&quot;https://tiddlywiki.com/&quot;&gt;TiddlyWiki&lt;/a&gt; is a brilliant piece of software. It is a single HTML file with a note-taking interface, where the notes you take are stored directly in the HTML file itself, so you can easily carry (copy) the file around and easily deploy it online for sharing. However, most modern browsers don’t allow web pages to access the filesystem, so in order to let &lt;a href=&quot;https://tiddlywiki.com/&quot;&gt;TiddlyWiki&lt;/a&gt; save the notes, you need to rely on &lt;a href=&quot;https://tiddlywiki.com/#%22savetiddlers%22%20Extension%20for%20Chrome%20and%20Firefox%20by%20buggyj&quot;&gt;browser extensions&lt;/a&gt; or Dropbox integration service like &lt;a href=&quot;https://github.com/Jermolene/TiddlyWiki-in-the-Sky&quot;&gt;TiddlyWiki in the Sky&lt;/a&gt;. But they still have some frictions.&lt;/p&gt;

&lt;p&gt;So recently I started to look for other alternatives for note-taking. 
&lt;!--more--&gt;
Here are some of the requirements I desire:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Notes are stored in non-proprietary format. In case the service/software is discontinued, I can easily migrate them to other tool.&lt;/li&gt;
  &lt;li&gt;Has some form of formatting, e.g. write in Markdown and render to HTML.&lt;/li&gt;
  &lt;li&gt;Auto-generated table of contents and search functionality.&lt;/li&gt;
  &lt;li&gt;Can be used offline and data is stored locally.&lt;/li&gt;
  &lt;li&gt;Can be easily shared with other people.&lt;/li&gt;
  &lt;li&gt;Can be version-controlled, and you won’t lose data if there is a conflict.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;https://tiddlywiki.com/&quot;&gt;TiddlyWiki&lt;/a&gt; excels at 1 to 5, and I can easily sync it with Dropbox. However, I often forgot to click “save” in TiddlyWiki, and in some case when I accidentally create some conflict while syncing, Dropbox simply creates two copies and I have to manually merge them. There is also no version history so it’s hard to merge and look into the history.&lt;/p&gt;

&lt;p&gt;Then I suddenly had an epiphany during shower: all I need is a bunch of Markdown files, version controlled by git, then I can use a static site generator to render them as HTML, with table of contents and client-side search. After some quick search I found &lt;a href=&quot;http://www.mkdocs.org/&quot;&gt;MkDocs&lt;/a&gt;, which is a Python-based static-site generator for project documentations. It also have a live-reloading development server, which I really love.&lt;/p&gt;

&lt;h1 id=&quot;using-mkdocs&quot;&gt;Using MkDocs&lt;/h1&gt;

&lt;p&gt;MkDocs is really straight-forward to setup and use (under Linux). To install, simply use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip&lt;/code&gt; (assuming you have Python and pip installed):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo pip install mkdocs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you can create your document folder using&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdocs new &amp;lt;project name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The generated folder will have the following structure:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;project name&amp;gt;
├── docs
│   └── index.md
└── mkdocs.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can then start to write documents in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docs&lt;/code&gt; folder. You can create subfolders to organize the Markdown files. To view the generated HTML, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd&lt;/code&gt; into the project folder, then run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdocs serve&lt;/code&gt;, the development server will start to serve on port 8000. Opening &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1:8000&lt;/code&gt; in your browser then you can see the document. You can also run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdocs build&lt;/code&gt; to generate the static HTML into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites&lt;/code&gt; folder. The folder can then be hosted using any server.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/mkdocs/mkdocs.png&quot; alt=&quot;MkDocs&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdocs.yml&lt;/code&gt; file contains some configuration. For example, you can use the ReadTheDocs theme by adding the line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;theme: readthedocs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/blog_assets/mkdocs/readthedocs.png&quot; alt=&quot;readthedocs&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you wish to open the generated site locally using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file://&lt;/code&gt; protocol, you can add this line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;use_directory_urls: false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you have a note named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo.md&lt;/code&gt;, the generated file will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/foo/index.html&lt;/code&gt;.  If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use_directory_urls&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;, the URL for linking to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo.md&lt;/code&gt; page will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/foo/&lt;/code&gt;, which is a more modern URL naming convention. But for this routing to work, you must host the files in a web server. If you want to open it locally, you need to set the config to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt; and all the link will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/foo/index.html&lt;/code&gt; instead.&lt;/p&gt;

&lt;h1 id=&quot;migrating-from-tiddlywiki&quot;&gt;Migrating from TiddlyWiki&lt;/h1&gt;

&lt;p&gt;Moving all the notes from TiddlyWiki to MkDocs is very easy. If you are using TiddlyWiki 5.x+, you can go to “Tools” right under the search box, there is a “export all” button. Export the tiddlers (notes) to CSV. Then you can use the &lt;a href=&quot;https://github.com/achabotl/tiddly2md&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tiddly2md&lt;/code&gt;&lt;/a&gt; script to convert the CSV to individual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.md&lt;/code&gt; files. If your tiddler has UTF-8 titles, you need to add a parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding=&apos;utf-8&apos;&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pd.read_csv()&lt;/code&gt; call in the script for it to work.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/mkdocs/tiddly.png&quot; alt=&quot;export_all&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The exported Markdown files will lose the tag information, so if you are using tag as folder structure like me, you’ll have to manually create folders to arrange them. Some tiddlers using the old WikiText format will also be empty (I use Markdown in my TiddlyWiki, but there are some old ones from the old versions). You can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls -lS&lt;/code&gt; to see which file has no content and manually fix them. After the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.md&lt;/code&gt; files are in place, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdocs&lt;/code&gt; as usual.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;MkDocs is a pretty simple but powerful tool for note-taking. You get all the benefit of editing the notes using plain text editor, and have them version controlled by git. But you also get a nicely rendered HTML version with search functionality. One thing I miss from TiddlyWiki is the ability to generate a single HTML file containing all the notes. (MkDocs generates a folder of separate HTML, CSS and JS files.) There are some scripts like &lt;a href=&quot;https://github.com/twardoch/mkdocs-combine&quot;&gt;mkdocs-combine&lt;/a&gt; that claims to do this using &lt;a href=&quot;https://pandoc.org/&quot;&gt;Pandoc&lt;/a&gt;, but I haven’t tested them.&lt;/p&gt;

</description>
        <pubDate>Tue, 02 Jan 2018 20:14:58 +0000</pubDate>
        <link>https://shinglyu.com/web/2018/01/02/taking-notes-with-mkdocs.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2018/01/02/taking-notes-with-mkdocs.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Make LastPass Work Across App and Website</title>
        <description>&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;: My stock broker outsourced their Android app to a third-party company, so LastPass treat the desktop website and Android app as different sites. Although I can save them separately in LassPass, their password won’t synchronize with each other.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;: LassPass do have &lt;a href=&quot;https://lastpass.com/support.php?cmd=showfaq&amp;amp;id=1256&quot;&gt;a way&lt;/a&gt; to “merge” two sites into one, but the UI for doing so is not in the password vault page nor in the individual site’s configuration. Instead, the list of “equivalent” sites are under your account settings.&lt;/p&gt;

&lt;p&gt;Here are the procedure to merge them:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Log-in to your LastPass desktop website. (I didn’t found the setting in the mobile app.)&lt;/li&gt;
  &lt;li&gt;Click your account icon on the top right and go to settings.
&lt;img src=&quot;/blog_assets/lastpass/account_settings.png&quot; alt=&quot;account settings&quot; /&gt;&lt;/li&gt;
  &lt;li&gt;In your account settings, choose the “Equivalent Domains” tab.
&lt;img src=&quot;/blog_assets/lastpass/equivalent.png&quot; alt=&quot;equivalent domains settings&quot; /&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Here you can add to the list of equivalent sites. For example my broker’s desktop site is using the domain “capital.com.tw”, but its app uses “mitake.com”, which is the outsource company that built the app. So I’ll add them into one entry so they are treated as equivalent.
&lt;img src=&quot;/blog_assets/lastpass/my_site.png&quot; alt=&quot;my_site&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Ta-dah! Now I can login to both the app and desktop site using the same LastPass entry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Side note&lt;/strong&gt;: Why I decided to use a password manager?&lt;/p&gt;

&lt;p&gt;I avoid using a password manager for quite a while. I always believe it’s a bad idea to have a single point of failure. But after listening to a presentation by the security expert in my company, I decided to give it a try. The presenter mentioned that although LassPass had suffered from a few breaches in the past, the main password vault was never compromised, and they were very transparent about how and what had happened. So after I moved to Amsterdam and had to register tons of new online services, I simply can’t come up with a good algorithm to design my password that works across all sites. The risk of LastPass being completely hacked is relatively low comparing to my one password used across all sites being breached. If I use one password across all sites, then the level of security I get is equal to the security level of the weakest site I use (e.g. &lt;a href=&quot;http://www.telegraph.co.uk/technology/2017/10/03/yahoo-says-3-billion-accounts-affected-2013-data-breach/&quot;&gt;Yahoo&lt;/a&gt;?), so I would rather bet on LastPass.&lt;/p&gt;

</description>
        <pubDate>Mon, 16 Oct 2017 18:59:46 +0000</pubDate>
        <link>https://shinglyu.com/web/2017/10/16/make-lastpass-work-across-app-and-website.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2017/10/16/make-lastpass-work-across-app-and-website.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Porting Chrome Extension to Firefox</title>
        <description>&lt;p&gt;Three years ago, I wrote the &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/focusblocker/?src=userprofile&quot;&gt;FocusBlocker&lt;/a&gt; to help me focus on my master thesis. It’s basically a website blocker that stops me from checking Facebook every five minute. But is different from other blockers like &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/leechblock/&quot;&gt;LeechBlock&lt;/a&gt; that requires you to set a fixed schedule. FocusBlocker lets you set a quota, e.g. I can browse 10 minutes of Facebook then block it for 50 minutes. So as long as you have remaining quota, you can check Facebook anytime. I’m glad that other people find it useful, and I even got my first donation through AMO because of happy users.&lt;/p&gt;

&lt;p&gt;Since this extension serves my need, I’m not actively maintaining it or adding new features. But I was aware of Firefox’s &lt;a href=&quot;https://blog.mozilla.org/addons/2015/08/21/the-future-of-developing-firefox-add-ons/&quot;&gt;transition from the legacy Add-on SDK to WebExtension API&lt;/a&gt;. So before WebExtension API is fully available, I started to migrate it to Chrome’s extension format. But I didn’t got the time to actually migrate it back to Firefox, until a user emails me asking for a WebExtension version. I looked into the statistics, the daily active user count drops from ~1000 to ~300. That’s when I rolled up my sleeve and actually migrated it in one day. (Although later I found out that the drop is not entirely due to users upgrading to newer Firefox, is because of &lt;a href=&quot;https://blog.mozilla.org/addons/2017/06/21/upcoming-changes-usage-statistics/&quot;&gt;this change&lt;/a&gt;.) Here is how I did it and what I’ve learned from the process.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/webext/daily_user.png&quot; alt=&quot;daily_user.png&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;what-needs-to-be-changed&quot;&gt;What needs to be changed&lt;/h1&gt;
&lt;p&gt;To evaluate the scope of the work. We need to first look at what APIs I used. The FocusBlocker Chrome version uses the three main APIs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome.tabs&lt;/code&gt;: to monitor new tabs opening and actually block existing tabs.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome.alarm&lt;/code&gt;: Set timers for blocking and unblocking.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome.storage.sync&lt;/code&gt;: To store the settings and persist the timer across browser restarts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s nice that these APIs are all supported (at least the parts I used) in Firefox, so I don’t really need to modify any JavaScript code.&lt;/p&gt;

&lt;p&gt;I loaded the manifest directly in Firefox’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;about:debugging&lt;/code&gt; page (you can also consider use the convenient &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Getting_started_with_web-ext&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-ext&lt;/code&gt;&lt;/a&gt; command line tool), but Firefox rejects it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/webext/about_debugging.png&quot; alt=&quot;about_debugging.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s because Firefox requires you to set a unique &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; for each extension(Not required if you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-ext run&lt;/code&gt;. You can read more about the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; requirement &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/WebExtensions_and_the_Add-on_ID&quot;&gt;here&lt;/a&gt;), and you should (but not mandatory) also set a minimal version of Firefox on which the extension works, like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;applications&quot;: {
  &quot;gecko&quot;: {
    &quot;id&quot;: &quot;focusblocker@shing.lyu&quot;,
    &quot;strict_min_version&quot;: &quot;48.0&quot;
  }
},
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There is one more modification need. In my Chrome extension I used the old &lt;a href=&quot;https://developer.chrome.com/extensions/options&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;options_page&lt;/code&gt; setting&lt;/a&gt; setting to set the preference page. But Firefox only support the newer &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/options_ui&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;options_ui&lt;/code&gt;&lt;/a&gt;. You can also apply browser’s system style for your settings page, so the UI looks like part of the Firefox setting. Firefox generalized the name from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome_style&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;browser_style&lt;/code&gt;. So this is what I need to add to my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest.json&lt;/code&gt; file (and remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;options_page&lt;/code&gt; setting):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;options_ui&quot;: {
  &quot;page&quot;: &quot;options.html&quot;,
  &quot;browser_style&quot;: true
},
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/webext/about_addon.png&quot; alt=&quot;about_addon.png&quot; /&gt;
&lt;img src=&quot;/blog_assets/webext/browser_style.png&quot; alt=&quot;browser_style.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s all I need to port the extension from Chrome to Firefox. Super easy! The WebExtension team really did a good job on making the extensions compatible. In case you are curious, you can find the full source code of FocusBlocker on &lt;a href=&quot;https://github.com/shinglyu/focusblocker&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;publishing-the-extension-on-amo&quot;&gt;Publishing the extension on AMO&lt;/h1&gt;

&lt;p&gt;To publish the extension on &lt;a href=&quot;https://addons.mozilla.org&quot;&gt;addons.mozilla.org&lt;/a&gt;, you need to zip all the files in a zip and upload it. Here are some tips for passing the review more easily.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If you already have a legacy extension listed, upload the WebExtension-API-backed extension to replace it, so your user will have an easier time upgrading.  (I chose to upload a separate listing, which is not recommended by the Add-on team.)&lt;/li&gt;
  &lt;li&gt;Don’t pack any unnecessary file into the zip, exclude all the temporary test files from the zip.&lt;/li&gt;
  &lt;li&gt;Remove or comment out all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log()&lt;/code&gt; calls. Although it’s not a strict requirement, but it will make the review process much smoother.&lt;/li&gt;
  &lt;li&gt;If you use any third party library, consider including (i.e. “vendoring”) the file into the zip, or at least upload the source for review.&lt;/li&gt;
  &lt;li&gt;If you’ve upload one version and you’d like to make some modifications or fix, you need to bump the version number, no matter how small the change is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Firefox is planning to completely roll out the new format in version 57 (around November, 2017). So if you have a legacy Firefox extension, or a Chrome extension you want to convert, now is a perfect timing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to try out the new FocusBlocker, please head to the &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/focusblocker-new/&quot;&gt;install page&lt;/a&gt;. You can also find the Chrome version &lt;a href=&quot;https://chrome.google.com/webstore/detail/focusblocker/bejdhniafighghjelnmhhcgongokdhbi?hl=en-US&quot;&gt;here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit (2017/8/30)&lt;/strong&gt;: Andreas from the Mozilla Add-on team points out a few errors, I’ve modified the original text to reflect that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Do NOT create a new version of the extension on AMO, upload and replace your legacy extension using the same listing.&lt;/li&gt;
  &lt;li&gt;The user drop is related to https://blog.mozilla.org/addons/2017/06/21/upcoming-changes-usage-statistics/&lt;/li&gt;
  &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-ext run&lt;/code&gt; should work without an ID&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strict_min_version&lt;/code&gt; is not mandatory&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Mon, 07 Aug 2017 06:37:22 +0000</pubDate>
        <link>https://shinglyu.com/web/2017/08/07/porting-chrome-extension-to-firefox.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2017/08/07/porting-chrome-extension-to-firefox.html</guid>
      </item>
      
    
    
    
    
    
    
    
      
      <item>
        <title>Install Ubuntu 16.04 on ThinkPad 13 (2nd Gen)</title>
        <description>&lt;p&gt;&lt;em&gt;It has been a while since my last post. I’ve been busy for the first half of this year but now I got more free time. Hopefully I can get back to my usual pace of one post per months.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My old laptop (Inhon Carbonbook, discontinued) had a swollen battery. I kept using it for a few months but then the battery squeezed my keyboard so I can no longer type correctly. After some research I decided to buy the ThinkPad 13 model because it provides descent hardware for its price, and the weight (~1.5 kg) is acceptable.
Every time I got a new computer the first thing is to get Linux up and running. So here are my notes on how to install Ubuntu Linux on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Everything works out of the box. Just remember to turn off secure boot and shrink the disk in Windows before you install.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;some-assumptions&quot;&gt;Some assumptions&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;I’m using &lt;a href=&quot;http://www3.lenovo.com/us/en/laptops/thinkpad/thinkpad-13-series/ThinkPad-13-Windows-2nd-Gen/p/22TP2TX133E&quot;&gt;ThinkPad 13 2nd Gen&lt;/a&gt; bought in June 2017&lt;/li&gt;
  &lt;li&gt;I’m using &lt;a href=&quot;http://releases.ubuntu.com/16.04/&quot;&gt;Ubuntu Linux 16.04.2 LTS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;I use Linux as my primary operating system, but still keeps Windows for rare occasions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;before-installing-ubuntu&quot;&gt;Before Installing Ubuntu&lt;/h1&gt;
&lt;p&gt;First we need to clean up some disk space for Ubuntu. But if you are going to delete Windows completely you can skip the following steps.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(In Windows) Right click on the start menu, select “PowerShell (administrator)”, then run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diskmgmt.msc&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Right click the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:&lt;/code&gt; disk and select the shrink disk option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we can install Ubuntu, we need to disable the secure boot feature in BIOS.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift&lt;/code&gt;+restart to be able to get into BIOS, otherwise you’ll keep booting into Windows.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt; followed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F1&lt;/code&gt; to go into BIOS during boot.&lt;/li&gt;
  &lt;li&gt;Disable Secure Boot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/linux_on_tp13/BIOS.JPG&quot; alt=&quot;BIOS&quot; /&gt;
&lt;img src=&quot;/blog_assets/linux_on_tp13/BIOS_secure_boot.JPG&quot; alt=&quot;secure_boot&quot; /&gt;
&lt;img src=&quot;/blog_assets/linux_on_tp13/BIOS_secure_boot_2.JPG&quot; alt=&quot;secure_boot_sub_menu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;UEFI boot seems to work with Ubuntu 16.04’s installer, so you can keep all the UEFI options enabled in the BIOS. Download the &lt;a href=&quot;http://releases.ubuntu.com/16.04/&quot;&gt;Ubuntu installation disk&lt;/a&gt; and use a bootable USD disk creator that supports UEFI, for example &lt;a href=&quot;https://rufus.akeo.ie&quot;&gt;Rufus&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;installing-ubuntu&quot;&gt;Installing Ubuntu&lt;/h1&gt;

&lt;p&gt;Installing Ubuntu should be pretty straightforward if you’ve done it before.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Go to BIOS again and set the USB drive as top priority boot medium.&lt;/li&gt;
  &lt;li&gt;Boot into Ubuntu, select “Install Ubuntu”.&lt;/li&gt;
  &lt;li&gt;Follow the installer wizard.&lt;/li&gt;
  &lt;li&gt;Select “Something else” when asked how to partition the disk.&lt;/li&gt;
  &lt;li&gt;Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux-swap&lt;/code&gt; partition of 4GB for 8GB of RAM. I followed &lt;a href=&quot;https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Storage_Administration_Guide/ch-swapspace.html#swap-extending-lvm2&quot;&gt;Redhat’s suggestion&lt;/a&gt;, but there are different theories out there, so use your own judgement here.&lt;/li&gt;
  &lt;li&gt;Create a EXT4 main disk and set the mount point to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;After the installer finished, reboot. You should see the GRUB2 menu. The Windows options should also work without trouble.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/linux_on_tp13/GRUB2.JPG&quot; alt=&quot;GRUB2&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;what-works&quot;&gt;What works?&lt;/h1&gt;
&lt;p&gt;Almost everything works out of the box. All the media keys, like volume control, brightness and WiFi and Bluetooth toggle is recognized by the built-in Unity desktop. But I am using i3 so I have to set them up myself, more on this later. The USB Type-C port is a nice surprise for me. It supports charging, so you can charge with any Type-C charger. I tested Apple’s Macbook charger and it works well. I also tested &lt;a href=&quot;https://www.apple.com/shop/product/MJ1L2AM/A/usb-c-vga-multiport-adapter&quot;&gt;Apple’s&lt;/a&gt; and &lt;a href=&quot;http://www.dell.com/en-us/shop/dell-adapter-usb-3-0-to-hdmi-vga-ethernet-usb-2-0/apd/470-abhh/handhelds-tablet-pcs&quot;&gt;Dell’s&lt;/a&gt; Type-C multi-port adapter and both works, so I can plug my external monitor and my keyboard/mouse to it so it works like a docking station.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/linux_on_tp13/media_btn.JPG&quot; alt=&quot;media_key&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A side note, I’m also glad to find that Ubuntu now use the Fcitx IME by default. Most of the IME bugs I found in previous versions and ibus are now gone.&lt;/p&gt;

&lt;h1 id=&quot;what-doesnt-work&quot;&gt;What doesn’t work?&lt;/h1&gt;
&lt;p&gt;Not that I’m aware of. The only complaint I have is that the Ethernet and Wi-Fi naming method has changed somehow (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enp0s31f6&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eth0&lt;/code&gt;). But I believe that is something we can fix by software. People also complain that the power button and the hinge is not very sturdy. But I guess that’s the compromise you have to make for the relatively cheap price.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/linux_on_tp13/power_btn.JPG&quot; alt=&quot;power_button&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;more-on-setting-up-media-keys-for-i3-window-manager&quot;&gt;More on setting up media keys for i3 window manager&lt;/h1&gt;
&lt;p&gt;Since I use the i3 window manager, I don’t have Unity to handling all my media keys’ functionality. But it’s not hard to set them up using the following i3 config:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bindsym XF86AudioRaiseVolume exec amixer -q set Master 2dB+ unmute
bindsym XF86AudioLowerVolume exec amixer -q set Master 2dB- unmute
bindsym XF86AudioMute        exec amixer -D pulse set Master toggle # Must assign device &quot;pulse&quot; to successfully unmute

# Only two level of brightness
bindsym XF86MonBrightnessUp    exec xrandr --ouptut eDP-1 --brightness 0.8
bindsym XF86MonBrightnessDown  exec xrandr --ouptut eDP-1 --brightness 0.5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The only drawback is that the LED indicator on the mute buttons mighty be out of sync with the real software state.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;Overall, ThinkPad 13 is a descent laptop for its price range. Ubuntu 16.04 works out of the box. No sweat! If you are looking for a good Linux laptop, but can’t afford ThinkPad X1 Carbon or Dell’s XPS 13/15, ThinkPad 13 might be a good choice.&lt;/p&gt;

</description>
        <pubDate>Tue, 11 Jul 2017 07:48:30 +0000</pubDate>
        <link>https://shinglyu.com/web/2017/07/11/install-ubuntu-16-04-on-thinkpad-13-2nd-gen.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2017/07/11/install-ubuntu-16-04-on-thinkpad-13-2nd-gen.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>An Overview of Asia Tech Conferences in 2017</title>
        <description>&lt;p&gt;I’ve been attending and even talking at tech conferences for some time. One of the challenge is to keep track of when those conference will take place. Also there is no single list of all conferences I’m interested. There are &lt;a href=&quot;http://lanyrd.com/&quot;&gt;some website&lt;/a&gt; that collects them, but they often missed some community-organized events in Asia. Or there are some community-maintained &lt;a href=&quot;https://wordpress.lokidea.com/blog/1531/&quot;&gt;list of open source conferences&lt;/a&gt; (Thanks Barney!), but they don’t include for-profit conferences.&lt;/p&gt;

&lt;p&gt;Therefore I build a simple website that collects all conferences I know in Asia, focusing on open source software, web, and startup:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://asia-confs.github.io/asia-tech-confs/en/&quot;&gt;https://asia-confs.github.io/asia-tech-confs/&lt;/a&gt;
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;#The Technology Stack
Since I don’t really need dynamic-generated content, I use the &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; static site generator. For the look and feel, I use the &lt;a href=&quot;https://getmdl.io/&quot;&gt;Material Design Lite&lt;/a&gt; (MDL) CSS framework. (I did try other material design frameworks like &lt;a href=&quot;http://materializecss.com/&quot;&gt;Materialize&lt;/a&gt; or &lt;a href=&quot;https://www.muicss.com/&quot;&gt;MUI&lt;/a&gt;, but MDL is the most mature and clean one I can find.)&lt;/p&gt;

&lt;p&gt;One of the challenge is to provide the list in different languages. I found a &lt;a href=&quot;https://github.com/mrzool/polyglot-jekyll&quot;&gt;plugin-free way&lt;/a&gt; to make Jekyll support I18N (Internationalization). The essence is to create language specific sub-directories like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;en/index.md&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zh_tw/index.md&lt;/code&gt;. Then put all language specific string in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.md&lt;/code&gt; files. One pitfall is that by adding another level of directory, the relative paths (e.g. path to CSS and JS files) might not work, so you might need to use absolute path instead. For Traditional and Simplified Chinese translation, I’m too lazy to maintain two copy of the data. So I use a &lt;a href=&quot;http://blog.markplace.net/trackback.php?id=150&quot;&gt;JavaScript snippet&lt;/a&gt; to do the translation on-the-fly.&lt;/p&gt;

&lt;h1 id=&quot;how-to-contribute&quot;&gt;How to Contribute&lt;/h1&gt;

&lt;p&gt;If you know any conference, meetup or event that should be on the list, please feel free to drop and email to &lt;a href=&quot;mailto:asia.confs@gamil.com&quot;&gt;asia.confs@gmail.com&lt;/a&gt;. Or you can create a pull request or file and issue to our &lt;a href=&quot;https://github.com/asia-confs/asia-tech-confs&quot;&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enjoy the conferences and Happy Chinese New Year!&lt;/p&gt;

</description>
        <pubDate>Sat, 21 Jan 2017 08:22:34 +0000</pubDate>
        <link>https://shinglyu.com/web/2017/01/21/an-overview-of-asia-tech-conferences-in-2017.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2017/01/21/an-overview-of-asia-tech-conferences-in-2017.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Vim QuickFix for Rust and Servo</title>
        <description>&lt;p&gt;While working on a compiled language like Rust, a typical workflow is compile -&amp;gt; find the errors in the compiler message -&amp;gt; find the file containing the error -&amp;gt; edit -&amp;gt; re-compile. But usually there are lots of errors scattered around the compiler log, and to identify the filename and line number, and manually open the file to the correct line in an editor is a tedious job. Vim’s &lt;a href=&quot;http://vimdoc.sourceforge.net/htmldoc/quickfix.html&quot;&gt;quickfix&lt;/a&gt; streamline the process by collecting the errors into the split panel in vim, and allow you to navigate through the errors using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:cnext&lt;/code&gt; (next error) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:cprev&lt;/code&gt; (previous command). While you navigate to an error, the corresponding source file will be opened in the main vim window and jump directly to the line where the error is.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/quickfix-rust/quickfix-1.png&quot; alt=&quot;quickfix-1.png&quot; /&gt;&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;When you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:make&lt;/code&gt; inside vim, vim will execute the external compile tool (default: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt;’), then the compiler output will be parsed to extract the file name, line number, etc. By default, vim understands &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Makefile&lt;/code&gt;-based projects with C/C++ compiler output. But you can change the behavior by setting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;makeprg&lt;/code&gt; (defines what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:make&lt;/code&gt; does) and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;errorformat&lt;/code&gt; (error message parsing pattern). (You can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:help options&lt;/code&gt; and read “22 running make and jumping to errors” for detail.)&lt;/p&gt;

&lt;h1 id=&quot;rustvim&quot;&gt;rust.vim&lt;/h1&gt;
&lt;p&gt;The good news is: you don’t need to write the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;makeprg&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;errorformat&lt;/code&gt; yourself. The &lt;a href=&quot;https://github.com/rust-lang/rust.vim&quot;&gt;rust.vim&lt;/a&gt; vim plugin will do that for you. Installation is pretty simple, based on which vim plugin manager you use, you’ll find the instruction on the project’s &lt;a href=&quot;https://github.com/rust-lang/rust.vim#installation&quot;&gt;GitHub README&lt;/a&gt;. I use &lt;a href=&quot;https://github.com/VundleVim/Vundle.vim&quot;&gt;Vundle&lt;/a&gt;, so I add the following line to my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;Plugin &lt;span class=&quot;s1&quot;&gt;&apos;rust-lang/rust.vim&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:PluginInstall&lt;/code&gt; in vim.&lt;/p&gt;

&lt;p&gt;Now for ordinary Rust project which uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt;, you can set the compiler as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt; by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:compiler cargo&lt;/code&gt;. Then if you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:make build&lt;/code&gt;, vim will execute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo build&lt;/code&gt; for you.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/quickfix-rust/cargo-build.png&quot; alt=&quot;cargo-build.png&quot; /&gt;
&lt;img src=&quot;/blog_assets/quickfix-rust/cargo-error.png&quot; alt=&quot;cargo-error.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can also pass other parameters like you’d to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt;, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:make test&lt;/code&gt; will run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the compilation finished, you will see the source code being loaded, but you don’t see the error message. That’s because we haven’t open the quickfix panel yet. Now type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:copen&lt;/code&gt; then you’ll see a list of errors like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/quickfix-rust/quickfix-1.png&quot; alt=&quot;quickfix-1.png&quot; /&gt;
&lt;img src=&quot;/blog_assets/quickfix-rust/quickfix-2.png&quot; alt=&quot;quickfix-2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The vim main window will load the source file where the first error is pointing to. After you finish fixing it, you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:cnext&lt;/code&gt; command to jump to the next one.&lt;/p&gt;

&lt;h1 id=&quot;servo&quot;&gt;Servo&lt;/h1&gt;
&lt;p&gt;This all works very well for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt;-based Rust projects, but what if you are working on Servo, which uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mach&lt;/code&gt; as the build tool? The solution is simple, you still run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:compiler cargo&lt;/code&gt; to get the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;errorformat&lt;/code&gt; setup. But then you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:set makeprg=./mach&lt;/code&gt; to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:make&lt;/code&gt; run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./mach&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to avoid typing the commands every time you restart vim, you can set them in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;, but if you work on Servo and other &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt;-based Rust projects at the same time, you can actually set a per-directory based configuration. For example, you can put a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.nvimrc&lt;/code&gt; if you use &lt;a href=&quot;https://neovim.io/&quot;&gt;NeoVim&lt;/a&gt;) in your Servo directory like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;compiler cargo
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;makeprg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;./mach

autocmd QuickFixCmdPost &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;^l]&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; nested cwindow
autocmd QuickFixCmdPost    l&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; nested lwindow&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The last two lines makes quickfix window show up automatically right after the compilation. You can omit them if you want.&lt;/p&gt;

&lt;p&gt;Vim will not load the local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; file by default to avoid running malicious &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt;s. You need to enable them in your global &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt; file by adding these lines:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;exrc 
&lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;secure&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s it! Now you can have different compiler setup in different projects.&lt;/p&gt;

</description>
        <pubDate>Sun, 25 Dec 2016 13:00:00 +0000</pubDate>
        <link>https://shinglyu.com/web/2016/12/25/vim-quickfix-for-rust-and-servo.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2016/12/25/vim-quickfix-for-rust-and-servo.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Beginner&apos;s guide to git rebasing and squashing</title>
        <description>&lt;p&gt;I wrote this post on the &lt;a href=&quot;https://github.com/servo/servo/wiki/Beginner&apos;s-guide-to-rebasing-and-squashing&quot;&gt;Servo wiki&lt;/a&gt; to help beginners getting started with rebasing and squashing, two of the most terrifying operations you’ll face if you are not familiar with git. I’m cross posting this here for people working on other projects.&lt;/p&gt;

&lt;p&gt;Big thanks to &lt;a href=&quot;https://github.com/Wafflespeanut&quot;&gt;Wafflespeanut&lt;/a&gt; who proofread the post, any error you found here is my own.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Suppose you’ve created a pull request following the &lt;a href=&quot;https://github.com/servo/servo/blob/master/CONTRIBUTING.md&quot;&gt;checklist&lt;/a&gt;, but the reviewer asks you to fix something, do a rebase or squash your commits, how exactly do you do that? If you have some experience with git, you might want to check the &lt;a href=&quot;https://github.com/servo/servo/wiki/Github-workflow&quot;&gt;GitHub workflow&lt;/a&gt; for a quick overview. But if you are not familiar with git enough, we’ll teach you how to do these common operations in detail.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h1 id=&quot;fixing-review-comments&quot;&gt;Fixing review comments&lt;/h1&gt;

&lt;p&gt;Once you reviewer reviewed your patch, he/she might leave some comments asking you to fix something. So you edit the source code, then you will probably do something like this.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git add &amp;lt;the file you fixed&amp;gt;&lt;/code&gt; then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit&lt;/code&gt;, write a commit message telling people what you’ve fixed. (You might also check out the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fixup&lt;/code&gt; option for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit&lt;/code&gt; in the &lt;a href=&quot;https://github.com/servo/servo/wiki/Github-workflow&quot;&gt;workflow doc&lt;/a&gt;.)&lt;/li&gt;
  &lt;li&gt;Simply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt; to the same remote branch which you’ve created the PR with. The GitHub pull request page will pick up your changes, and hide those review comments you’ve fixed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your fix is trivial, and you have a single commit ready for merge, then you can consider using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git commit --amend&lt;/code&gt; to add the change directly to your last commit. Then, all you need to do is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push -f&lt;/code&gt; to force push to the branch at your fork.&lt;/p&gt;

&lt;h1 id=&quot;rebasing&quot;&gt;Rebasing&lt;/h1&gt;

&lt;p&gt;Sometimes, if someone merged new code while your patch is still in review, git might not be able to figure out how to apply your patch on top of the new code. In this case, our &lt;a href=&quot;https://github.com/bors-servo&quot;&gt;bors-servo&lt;/a&gt; bot will notify you with a helpful message:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;☔️ The latest upstream changes (presumably #12345) made this pull request unmergeable. Please resolve the merge conflicts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;… and the GitHub UI will say “This branch has conflicts that must be resolved”. This is when you need a rebase. Here, we’ll explain the power of rebasing with a simple example.&lt;/p&gt;

&lt;p&gt;Suppose the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servo/servo&lt;/code&gt; tree is like this before you start working on a bug, in which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; is the latest commit:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;P -&amp;gt; Q -&amp;gt; R (&amp;lt;= remote servo/servo)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You create a new branch from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt;, then you add your own fixes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;P -&amp;gt; Q -&amp;gt; R   (&amp;lt;= remote servo/servo)
           \
            .--&amp;gt; X -&amp;gt; Y (&amp;lt;= your local branch)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But if someone merged their PR &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; before you, and he/she modified the code which you had also been working on, then git might fail to know how to merge the changes from both of you. So, we should fix this by ourselves.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;P -&amp;gt; Q -&amp;gt; R ---&amp;gt; S
           \
            .--&amp;gt; X -&amp;gt; Y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But you &lt;strong&gt;cannot&lt;/strong&gt; do that with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull&lt;/code&gt;, because it will create a &lt;a href=&quot;https://www.atlassian.com/git/tutorials/merging-vs-rebasing/workflow-walkthrough&quot;&gt;merge commit&lt;/a&gt;, which will mess up the git history. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull&lt;/code&gt; will make the tree look like this, in which the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M&lt;/code&gt; commit contains stuff from your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;P -&amp;gt; Q -&amp;gt; R ---&amp;gt; S -------.
           \               \
            \               v
             .--&amp;gt; X -&amp;gt; Y -&amp;gt; M
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We want to fix this by rebasing, which means re-attach our changes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; to the new root &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt;, like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;P -&amp;gt; Q -&amp;gt; R -&amp;gt; S
                \
                 .--&amp;gt; X -&amp;gt; Y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here is how we do it:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Let’s assume your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servo&lt;/code&gt; repo is cloned from your own fork, (if you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git remote -v&lt;/code&gt;, you can see the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin&lt;/code&gt; points to something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git@github.com:&amp;lt;your username&amp;gt;/servo.git&lt;/code&gt; rather than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git@github.com:servo/servo.git&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;You need to create a new remote called ‘upstream’ that points to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servo/servo&lt;/code&gt; branch, so we can download the latest code. Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git remote add upstream git@github.com:servo/servo.git&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Now let’s download the latest code from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servo/servo&lt;/code&gt;, but don’t try to merge them: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git fetch upstream&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Now we can rebase our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; on top of the latest change, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase upstream/master&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Git might ask you to fix conflicts. We’ll get into that now.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;fixing-conflicts&quot;&gt;Fixing conflicts&lt;/h2&gt;

&lt;p&gt;First run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git status&lt;/code&gt;, it will tell you which file was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;both modified&lt;/code&gt;. Open those files one by one in them, you’ll see lines like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
use std::cmp::{max, min};
===========
use std::cmp::{max, PartialEq};
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; Your Commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This means that in the commit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt;, the author wants to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;min&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt; line, but in your commit you want to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PartialEq&lt;/code&gt;. (Lines between the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt; are the version on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servo/servo&lt;/code&gt;; the lines between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; are your local version.) A way to fix this is to include both, so you can delete all the lines from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt;, and replace them with the correct code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;use std::cmp::{max, min, ParitalEq};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After you fixed all the conflicts, you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git add &amp;lt;the files you edited&amp;gt;&lt;/code&gt;, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase --continue&lt;/code&gt;. You might need to repeat this action multiple times until every conflict is resolved. (In case you messed up, you can always run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase --abort&lt;/code&gt; to start over).&lt;/p&gt;

&lt;h1 id=&quot;squashing&quot;&gt;Squashing&lt;/h1&gt;

&lt;p&gt;Once the reviewer approves your PR, he/she might ask you to “squash” the commits. There are a lot of reasons for this. If you have a lot of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fixup&lt;/code&gt; commits, and you merge all of them directly into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;servo/servo&lt;/code&gt;, the git history will be bloated (which is something we don’t want). Or, if your recent commit fixes your previous commit in the same PR, then you could’ve simply rebased it (we prefer fixing the mistakes made by you).&lt;/p&gt;

&lt;p&gt;Anyway, using the last example, if your change consists of two commits &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt;, we want to squash them into a single commit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Z&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;P -&amp;gt; Q -&amp;gt; R
           \
            .--&amp;gt; X -&amp;gt; Y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;P -&amp;gt; Q -&amp;gt; R
           \
            .--&amp;gt; Z
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To achieve this, we can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase -i&lt;/code&gt; command.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;First we need to identify the last commit before the ones you want to merge, which is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; in our example. Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt; and remember the hash of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase -i &amp;lt;hash of R&amp;gt;&lt;/code&gt;, this will bring up your default text editor with a content like:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; pick 7de252c X
 pick 02e5bd1 Y

 # Rebase 170afb6..02e5bd1 onto 170afb6 (2 command(s))
 #
 # Commands:
 # p, pick = use commit
 # r, reword = use commit, but edit the commit message
 # e, edit = use commit, but stop for amending
 # s, squash = use commit, but meld into previous commit
 # f, fixup = like &quot;squash&quot;, but discard this commit&apos;s log message
 # x, exec = run command (the rest of the line) using shell
 # d, drop = remove commit
 #
 # These lines can be re-ordered; they are executed from top to bottom.
 #
 # If you remove a line here THAT COMMIT WILL BE LOST.
 #
 # However, if you remove everything, the rebase will be aborted.
 #
 # Note that empty commits are commented out
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Keep the first commit as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pick&lt;/code&gt;, and change all the other &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pick&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;squash&lt;/code&gt; (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; for short):&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; pick 7de252c X
 squash 02e5bd1 Y

 # Rebase 170afb6..02e5bd1 onto 170afb6 (2 command(s))
 ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Now save and quit the text editor, the rebase will run until the end. You might meet conflicts like you do in rebasing. Fix then using the same method described in the previous section.&lt;/li&gt;
  &lt;li&gt;After the rebase is finished, the editor will pop-up again, now you can write the commit message for the new commit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Z&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push -f&lt;/code&gt; to push the squashed commit to GitHub (and update the PR).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you made any mistake right after you run step 2, you can abort by deleting every line in the text editor then save and exit. If you mess up fixing the conflicts, you can also run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase --abort&lt;/code&gt; to reset everything and start over.&lt;/p&gt;

&lt;h1 id=&quot;ask-for-help&quot;&gt;Ask for help&lt;/h1&gt;

&lt;p&gt;Working on git for a personal project is very different from collaborating on giant open-source projects like Servo. So, if this guide doesn’t solve your problem, feel free to ask your reviewer in the pull request or ask people on IRC (&lt;a href=&quot;http://chat.mibbit.com/?server=irc.mozilla.org&amp;amp;channel=%23servo&quot;&gt;#servo&lt;/a&gt; on irc.mozilla.org).&lt;/p&gt;

</description>
        <pubDate>Tue, 08 Nov 2016 06:29:17 +0000</pubDate>
        <link>https://shinglyu.com/web/2016/11/08/servo-rebase-and-squash-guide.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2016/11/08/servo-rebase-and-squash-guide.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Mutation Testing in JavaScript Using Stryker</title>
        <description>&lt;p&gt;Earlier this year, I wrote a &lt;a href=&quot;http://shinglyu.github.io/testing/2016/02/15/Mutation_Testing_in_JavaScript_Using_Grunt_Mutation_Testing.html&quot;&gt;blog post&lt;/a&gt; introducing Mutation Testing in JavaScript using the &lt;a href=&quot;https://www.npmjs.com/package/grunt-mutation-testing&quot;&gt;Grunt Mutation Testing framework&lt;/a&gt;. But as their NPM README said,&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We will be working on (gradually) migrating the majority of the code base to the &lt;a href=&quot;https://www.npmjs.com/package/stryker&quot;&gt;Stryker&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I’ll update my post to use the latest Stryker framework. The following will be the updated post with all the code example migrated to the Stryker framework:&lt;/p&gt;

&lt;!--more--&gt;
&lt;hr /&gt;

&lt;ul&gt;
  &lt;li&gt;Code example: &lt;a href=&quot;https://github.com/shinglyu/JS-mutation-testing-example-stryker&quot;&gt;github link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last November (2015) I attended the EuroStar Software Testing Conference, and was introduced to a interesting idea called mutation testing. Ask yourself: “How do I ensure my (automated) unit test suite is good enough?”. Did you miss any important test? Is your test always passing so it didn’t catch anything? Is there anything un-testable in your code such that your test suite can never catch it?&lt;/p&gt;

&lt;h1&gt;Introduction to Mutation Testing&lt;/h1&gt;

&lt;p&gt;Mutation testing tries to “test your tests” by deliberately inject faults (called “mutants”) into your program under test. If we re-run the tests on the crippled program, our test suite should catch it (i.e. some test should fail.) If you missed some test, the error might slip through and the test will pass. Borrowing terms from Genetics, if the test fails, we say the mutant is “killed” by the test suite; on the opposite, if the tests passes, we say the mutant survived.&lt;/p&gt;

&lt;p&gt;The goal of mutation testing is to kill all mutants by enhancing the test suite. Although 100% kill is usually impossible for even small programs, any progress on increasing the number can still benefit your test a lot.&lt;/p&gt;

&lt;p&gt;The concept of mutation testing has been around for quite a while, but it didn’t get very popular because of the following reasons: first, it is slow. The number of possible mutations are just too much, re-compiling (e.g. C++) and re-run the test will take too long. Various methods has been proposed to lower the number of tests we need to run without sacrifice the chance of finding problems. The second is the “equivalent mutation” problem, which we’ll discuss in more detail in the examples.&lt;/p&gt;

&lt;h1&gt;Mutation Testing from JavaScript&lt;/h1&gt;

&lt;p&gt;There are many existing mutation testing framework. But most of them are for languages like Java, C++ or C#. Since my job is mainly about JavaScript (both in browser and Node.js), I wanted to run mutation testing in JavaScript.&lt;/p&gt;

&lt;p&gt;I have found a few mutation testing frameworks for JavaScript, but they are either non-open-source or very academic. The most mature one I can find so far is the &lt;a href=&quot;https://stryker-mutator.github.io/&quot;&gt;Stryker&lt;/a&gt; framework, which is released under Apache 2.0 license.&lt;/p&gt;

&lt;p&gt;This framework supports mutants like changing the math operators, changing logic operators or even removing conditionals and block statements. You can find a full list of mutations with examples &lt;a href=&quot;https://stryker-mutator.github.io/mutators.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;Setting up the environment&lt;/h1&gt;

&lt;p&gt;You can follow the &lt;a href=&quot;https://stryker-mutator.github.io/quickstart.html&quot;&gt;quickstart&lt;/a&gt; guide to install everything you need. The guide has a nice interactive menu so you can choose your favorite build system, test runner, test framework and reporting format. In this blog post I’ll demonstrate with my favorite combination: Vanilla NPM + Mocha + Mocha + clear-text.&lt;/p&gt;

&lt;p&gt;You’ll need &lt;code&gt;node&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt; installed. (I recommended using &lt;a href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;There are a few Node packages you need to install,&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--save-dev&lt;/span&gt; mocha
npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--save-dev&lt;/span&gt; stryker styker-api stryker-mocha-runner&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here are the list of packaged that I’ve installed:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;devDependencies&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;^2.5.3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;^0.4.3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stryker-api&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;^0.2.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stryker-mocha-runner&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;^0.1.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1&gt;A simple test suite&lt;/h1&gt;

&lt;p&gt;I created a simple program in &lt;code&gt;src/calculator.js&lt;/code&gt;, which has two functions:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// ===== calculator.js =====&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt;  &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The first function is called &lt;code&gt;substractPositive&lt;/code&gt;, it substract &lt;code&gt;num2&lt;/code&gt; from &lt;code&gt;num1&lt;/code&gt; if &lt;code&gt;num1&lt;/code&gt; is a positive number. If &lt;code&gt;num1&lt;/code&gt; is not positive, it will return &lt;code&gt;0&lt;/code&gt; instead. It doesn’t make much sense, but it’s just for demonstrative purpose.&lt;/p&gt;

&lt;p&gt;The second is a simple &lt;code&gt;add&lt;/code&gt; function that adds two numbers. It has a unnecessary &lt;code&gt;if...else...&lt;/code&gt; statement, which is also used to demonstrate the power of mutation testing.&lt;/p&gt;

&lt;p&gt;The two functions are tested using &lt;code&gt;test/test_calculator.js&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../src/calculator.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Calculator&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;substractPositive&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is a test file running using &lt;code&gt;mocha&lt;/code&gt;, The first verifies &lt;code&gt;substractPositive(1, -1)&lt;/code&gt; returns &lt;code&gt;2&lt;/code&gt;. The second tests &lt;code&gt;add(1,1)&lt;/code&gt; produces &lt;code&gt;2&lt;/code&gt;. If you run &lt;code&gt;mocha&lt;/code&gt; in your commandline, you’ll see both the test passes. If you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mocha&lt;/code&gt; in the commandline you’ll see the output:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;% mocha

  Calculator
    ✓ substractPositive
    ✓ add

  2 passing &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;6ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So this test suite looks OK, it exercises both function and verifies its output, but is it good enough? Let’s verify this by some mutation testing.&lt;/p&gt;

&lt;h1&gt;Running the mutation testing&lt;/h1&gt;

&lt;p&gt;To run the mutation testing, we first need to create a config file for the stryker mutator. Create a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stryker.conf.js&lt;/code&gt; in the project’s root directory and paste the following input into it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// stryker.conf.js&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Add your files here, this is just an example:&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;pattern&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;src/**/*.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;mutated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;included&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test/**/*.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;testRunner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;testFramework&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mocha&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;testSelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;reporter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;clear-text&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can see we choose &lt;code&gt;mocha&lt;/code&gt; as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testRunner&lt;/code&gt; and &lt;code&gt;testFramework&lt;/code&gt;, and we tell the Stryker framework to run the test files in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/**/*.js&lt;/code&gt;, and the source files are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/**/*.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then we need to tell &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm&lt;/code&gt; how to run Stryker in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; file. Simply add the following code in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; configuration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;scripts&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stryker -c stryker.conf.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm run stryker&lt;/code&gt; command to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm run&lt;/code&gt; and execute the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stryker -c stryker.conf.js&lt;/code&gt; script.&lt;/p&gt;

&lt;h1 id=&quot;finding-missing-tests&quot;&gt;Finding Missing Tests&lt;/h1&gt;

&lt;p&gt;Now if we run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm run stryker&lt;/code&gt;, you’ll see the following test result:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;51.997&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;InputFileResolver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Found&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mutated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;52.129&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Stryker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Initial&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;succeeded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ran&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;52.151&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Stryker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;generated&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;52.153&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;TestRunnerOrchestrator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Creating&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;runners&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;based&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cpu&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;SSSSSSSSSSSSS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;......&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BlockStatement&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Tests&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ran&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Tests&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ran&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;RemoveConditionals&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Tests&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ran&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BlockStatement&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Tests&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ran&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;//(omitted for brevity)&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mutants&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tested&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mutants&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;untested&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mutants&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timed&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mutants&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;killed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutation&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;based&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;covered&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;36.36&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutation&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;based&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;36.36&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;07&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;52.468&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;INFO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fileUtils&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Cleaning&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;temp&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;folder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see, it tested 22 kinds of mutations, but 14 (22-8=14) of them survived!&lt;/p&gt;

&lt;p&gt;Let’s look at a survived mutant:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ConditionalBoundary&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It tells us that by replacing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;=&lt;/code&gt; in line 2 of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculator.js&lt;/code&gt; file, the test will pass.&lt;/p&gt;

&lt;p&gt;If you look the line, the problem is pretty clear&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// ===== calculator.js =====&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;== This line&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We didn’t test the boundary values, if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num1 == 0&lt;/code&gt;, then the program should go to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;else&lt;/code&gt; branch and returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;. By changing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;=&lt;/code&gt;, the program will go into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return num1 - num2&lt;/code&gt; branch instead and returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0 - num2&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;This is one of the problem mutation testing can solve, it tells you which test case you missed. The solution is very simple, we can add a test like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;substractPositive&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;substractPositive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you run mutation testing again, the problem with the &lt;code&gt;substractPositive&lt;/code&gt; function should go away.&lt;/p&gt;

&lt;h1&gt;Equivalent Mutation and Dead Code&lt;/h1&gt;

&lt;p&gt;Sometimes a mutation will not change the behavior of the program, so no matter what test you write, you can never make it fail. For example, a mutation may disable caching in your program, the program will run slower but the behavior will be exactly the same, so you’ll have a mutation you can never kill. This kind of mutation is called “equivalent mutation”.&lt;/p&gt;

&lt;p&gt;Equivalent mutation will make you overestimate your mutation survival rate. And they time to debug, but may not reveal useful information about your test suite. However, some equivalent mutations do reveal issues about your program under test.&lt;/p&gt;

&lt;p&gt;Let look at the mutation results again:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ReverseConditional&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;RemoveConditionals&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BlockStatement&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Mutant&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;survived&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;sr&quot;&gt;/home/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shinglyu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Mutator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you look at the code, you’ll find that all the branches of the &lt;code&gt;if...else...&lt;/code&gt; statement returns the same thing. So no matter how you mutate the &lt;code&gt;if...else...&lt;/code&gt; conditions, or even remove one branch that was not reached, the function will always return the correct result.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Because we found that the &lt;code&gt;if...else...&lt;/code&gt; is useless, we can simplify the function to only three lines:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;num2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you run the mutation test again, you can see all the mutations being killed.&lt;/p&gt;

&lt;p&gt;This is one of the side benefit of equivalent mutations, although your test suite is fine, it tells you that your program code has dead code or untestable code.&lt;/p&gt;

&lt;h1&gt;Next Steps&lt;/h1&gt;

&lt;p&gt;By now you should have a rough idea about how mutation testing works, and how to actually apply them in your JavaScript project. If you are interested in mutation testing, there are more interesting questions you can dive into, for example, how to use code coverage data to reduce the test you need to run? How to avoid equivalent mutations? I hope you’ll find many interesting methods you can apply to your testing work. You can submit issues and suggestions to the Stryker &lt;a href=&quot;https://github.com/stryker-mutator/stryker&quot;&gt;GitHub repository&lt;/a&gt;, or even contribute code to them. The team is very responsive and friendly. Happy Mutation Testing!&lt;/p&gt;
</description>
        <pubDate>Tue, 11 Oct 2016 01:00:00 +0000</pubDate>
        <link>https://shinglyu.com/testing/2016/10/11/Mutation_Testing_in_JavaScript_Using_Stryker.html</link>
        <guid isPermaLink="true">https://shinglyu.com/testing/2016/10/11/Mutation_Testing_in_JavaScript_Using_Stryker.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>2016 COSCUP, Modern Web and Taiwan Code Sprint</title>
        <description>&lt;p&gt;August was quite a busy month for me. I went to two conferences and one workshop to promote Servo and Rust. Here are what I’ve observed in those events.&lt;/p&gt;

&lt;h2 id=&quot;coscup&quot;&gt;COSCUP&lt;/h2&gt;

&lt;p&gt;COSCUP is one of the largest volunteer-organized conference on open source software in Taiwan. When I was a student I tried to attend all the sessions, wishing to learn from the talks. But in recent years I realize that it’s really not about the talks, but more about meeting people. Half of the talks are about introducing people to new tools or libraries. You can learn them by yourself more efficiently by reading tutorial articles online. The most rewarding part is actually socializing with people. Because most of the open source participants will attended this conference, you can easily meet a lot of new friends through mutual friends. So this year I spend most of my time at social events, for example the speaker dinner party, Mozilla BoF (bird of a feather), and chatting with my friend’s friends.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;But I still listened to quite a few interesting sessions. This year’s talk (&lt;a href=&quot;http://coscup.org/2016/schedules.html&quot;&gt;schedule&lt;/a&gt;) has a high percentage of Internet of Things talk. But they are still fundamental programming stuff like Linux kernel and LLVM.  There are also a lot of presence of Mozilla-related topics: connected devices projects like Webby End SensorWeb, Bob Chao’s talk on Mozilla Community Space Project, and Gary Kwong talked about fuzzy testing in the Unconf. We also have a booth for Mozilla, in which we show off the Taiwan community. I also attended the BoF and had a nice chat with the community. My talk also went pretty well, I’ll talk more about them later.&lt;/p&gt;

&lt;p&gt;Overall it was a nice event. I meant a lot of old and new friends, including two or three people approaching me saying that they want to contribute to Servo. The atmosphere is pretty relaxed, it feels more like an open source community reunion than a technical conference. One of the best idea I saw in the conference is the souvenir sticker collection book. This solves a huge problem we tech geeks have: having too many stickers but too little laptop surface area.&lt;/p&gt;

&lt;h2 id=&quot;modern-web&quot;&gt;Modern Web&lt;/h2&gt;

&lt;p&gt;The second conference I attended is the &lt;a href=&quot;http://modernweb.tw/&quot;&gt;Modern Web Conference&lt;/a&gt; hosted by iThome, a technical publisher in Taiwan. The conference is much more expensive (~$200 USD) so the logistics are better. Each room has a dedicated AV team which handles the projector, audio and video recording. A good arrangement is that after a talk, the speaker can move to a Q&amp;amp;A space outside the meeting room, so people can ask follow-up questions without blocking the next session. They also have a cool Dojo booth, you can get a 15 minute hands-On tutorial on topics like frontend testing and responsive CSS design, etc.&lt;/p&gt;

&lt;p&gt;The hottest topics in the conference is React.js and React Native. A lot of companies are trying to merge their frontend and Android/iOS app team, to encourage code sharing and create a unified experience across platforms. To my surprise, most them are pretty satisfied with the solution, I believe that’s partially because for most of them, the app is not the most critical factor in their business. They just want to have some presence in every platform, so having one or two taking care of all the platform sound like a better investment then having dedicated engineer for each platform. There is even a thing called “React Native for Web”, we are porting web to native, now we are porting them back to web?! There is also a high interest in backend performance tuning. There are talks about NGINX performance tuning, container orchestration, and various tricks for speeding up your website from even the network protocol level.  I also heard an interesting talk about the status of HTML emails. Although I know HTML email is hard to work with, I didn’t know the support is so different across mail clients.&lt;/p&gt;

&lt;h2 id=&quot;the-servo-talks&quot;&gt;The Servo Talks&lt;/h2&gt;

&lt;p&gt;I gave roughly the same talk in the above two conferences. The COSCUP audiences are more interested in the system library side of Servo, they asked about do we have Wayland support (yes, &lt;a href=&quot;https://blogs.s-osg.org/community-driven-wayland-support-servo/&quot;&gt;we do&lt;/a&gt;), and the status of our media support through ffmpeg. The Modern Web crowd are less interested in our internal implementation, but on how and when Servo will become a real product. They also asked a lot about the future of Servo together with Firefox, which I think might get clearer in the following months.&lt;/p&gt;

&lt;p&gt;WebRender’s performance and its promise to allow web dev to write 60 fps animation without understanding the internal implementation resonates well with the audiences. There was a talk in Modern Web about how to get the best CSS animation performance, which is basically about which API for avoid, hopefully with WebRender we don’t need to worry about them anymore. They also wants to know the detail about out Web Platform Test effort. In general, people excited rather then worried (by the need to support yet another browser) about Servo. The doge sticker also helped me attracting the crowd! The Modern Web staff also helped me making the sticker into this cute button:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/doge_button.jpg&quot; alt=&quot;doge button&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;taiwan-code-sprint&quot;&gt;Taiwan Code Sprint&lt;/h2&gt;

&lt;p&gt;This is the third event I attended in one week, and also the most exhausting one. This is a new event hosted by a bunch of collage students, who wants to connect potential open source project contributors with mentors. I signed up as a Servo mentor and received roughly six applications. They rented two classrooms in the National Normal University in Taipei, and all mentors and students stay there for 2 days to hack on easy issues. There are people from Firefox,  Ubuntu, LibreOffice, Linux Kernel, etc.&lt;/p&gt;

&lt;p&gt;Thanks to the help from the Servo community around the world, especially jdm and KiChjang, we were able to get around 14 patches landed in just two days. The new contributors are very impressed by the friendliness of the contribution flow. What confuses them is some Rust-specific syntax like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;match&lt;/code&gt;ing on Rust’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enum&lt;/code&gt;, or working with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unwrap()&lt;/code&gt;. Some of the contributors who are not familiar with the GitHub workflow needs some help with rebasing, pushing new modifications and squashing, but in general they are able to work through the easy bugs pretty fast.&lt;/p&gt;

&lt;p&gt;With six of the new contributors joining the Servo project, we now have about fifteen Servo contributors in Taiwan. Our Rust book study group also finished reading the basic part of the book, and we are moving on to build side projects in Rust. Hopefully we can create a strong Servo/Rust community here in Taiwan!&lt;/p&gt;

</description>
        <pubDate>Sun, 04 Sep 2016 12:40:00 +0000</pubDate>
        <link>https://shinglyu.com/web/2016/09/04/2016-conferences.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2016/09/04/2016-conferences.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Identify Performance Regression in Servo</title>
        <description>&lt;p&gt;Performance has always been a key focus for the Servo browser engine project. But just measure the performance through profilers and benchmarks is not enough. The first impression to a real user is the page load time. Although many internal, non-visible optimizations are important, we still want to make sure our page load time is doing well.&lt;/p&gt;

&lt;p&gt;Back in April, I opened this bug &lt;a href=&quot;https://github.com/servo/servo/issues/10452&quot;&gt;#10452&lt;/a&gt; to start planning the page load test. With the kind advice from the Servo community and the Treeherder people, we finally settled for a test design similar to the Talos test suite, and decided to use Perfherder for visualization.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;test-design&quot;&gt;Test Design&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.mozilla.org/Buildbot/Talos&quot;&gt;Talos&lt;/a&gt; is a performance test suite designed for Gecko, the browser engine for Firefox. It has many different kinds of tests, covering user-level UI testing and benchmarking. But what we really care about is the &lt;a href=&quot;https://wiki.mozilla.org/Buildbot/Talos/Tests#tp5&quot;&gt;TP5&lt;/a&gt; page load test suite. As the &lt;a href=&quot;https://wiki.mozilla.org/Buildbot/Talos/Tests#tp5&quot;&gt;wiki&lt;/a&gt; says, TP5 use Firefox to load 51 scrapped websites selected from the &lt;a href=&quot;http://www.alexa.com/topsites&quot;&gt;Alexa Top 500&lt;/a&gt; sites of its time. Those sites are hand-picked, then downloaded and cleaned to remove all external web resources. Then these web pages are hosted on a local server to reduce network latency impact.&lt;/p&gt;

&lt;p&gt;Each page is tested three times for Servo, then we take the medium of the three. (We should test more times, but it will take too long.) Then all the mediums are averaged using &lt;a href=&quot;https://en.wikipedia.org/wiki/Geometric_mean&quot;&gt;geometric mean&lt;/a&gt;. Geometric mean has a great property that even if two test results are of different scale (e.g. 500 ms v.s. 10000 ms), if any one of them changed by 10%, they will have equal impact on the average.&lt;/p&gt;

&lt;h2 id=&quot;visualization&quot;&gt;Visualization&lt;/h2&gt;

&lt;p&gt;Talos test results for Gecko have been using Treeherder and Perfherder for a while. The former is a dashboard for test results per commit; the latter is a line plot visualization for the Talos results. With the help from the Treeherder team, we were able to push Servo performance test results to the Perfherder dashboard. I had a &lt;a href=&quot;http://shinglyu.github.io/web/2016/05/07/visualizing_performance_data_on_perfherder.html&quot;&gt;blog post&lt;/a&gt; on how to do this. You’ll see screenshots for Treeherder and Perfherder in the following sections.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;We created a &lt;a href=&quot;https://github.com/shinglyu/servo-perf/blob/master/runner.py&quot;&gt;python test runner&lt;/a&gt; to execute the test. To minimize the effect of hardware differences, we run the Vagrant (VirtualBox backend) virtual machine used in Servo’s CI infrastructure. (You can find the Vagrantfile &lt;a href=&quot;https://github.com/servo/saltfs/blob/master/Vagrantfile&quot;&gt;here&lt;/a&gt;). The test is scheduled by &lt;a href=&quot;http://buildbot.net/&quot;&gt;buildbot&lt;/a&gt; and runs every midnight.&lt;/p&gt;

&lt;p&gt;The test results are collected into a JSON file, then consumed by the test result &lt;a href=&quot;https://github.com/shinglyu/servo-perf/blob/master/submit_to_perfherder.py&quot;&gt;uploader script&lt;/a&gt;. The uploader script will format the test result, calculate the average and push the data to Treeherder/Perfherder throught the &lt;a href=&quot;http://treeherder.readthedocs.io/submitting_data.html&quot;&gt;Python client&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-25-speedup&quot;&gt;The 25% Speedup!&lt;/h2&gt;

&lt;p&gt;A week before the Mozilla London Workweek, we found a big gap in the Perfherder graph. The average page load time changed from about 2000 ms to 1500 ms on June 10th.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/drop_graph.png&quot; alt=&quot;Improvement graph&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We were very excited about the significant improvement. Perfherder conveniently links to the commits in that build, but there are 26 commits in between.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/drop_graph_commits.png&quot; alt=&quot;Link to commits&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/commits_full.png&quot; alt=&quot;GitHub commits&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You should notice that there are many commits by the “bors-servo” bot, who is out automatic CI bot that does the pre-commit testing and auto-merging. Those commits are the merge commits generated when the pull request is merged. Other commits are commits from the contributors branch, so they may appear earlier then the corresponding merge commit. Since we only care when the commit gets merged to the master branch, not when the contributor commits to their own branch, we’ll only bisect the merge commits by bors-servo.&lt;/p&gt;

&lt;p&gt;Buildbot provides a convenient web interface for forcing a build on certain commits.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/buildbot_force.png&quot; alt=&quot;Buildbot force build&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can simply type the commit has in the “Revision” field and buildbot will checkout that commit, build it and run all the tests.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/force_zoom.png&quot; alt=&quot;Buildbot force build zoom in&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can track the progress on the Buildbot waterfall dashboard.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/buildbot_waterfall.png&quot; alt=&quot;Buildbot waterfall&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, you’ll be able to see the test result on Treeherder and Perfherder.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/treeherder.png&quot; alt=&quot;Treeherder&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/servo-perf/bisect_data.png&quot; alt=&quot;Perfherder with bisects&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The performance improvement turns out to be the result of this &lt;a href=&quot;https://github.com/servo/servo/pull/11513&quot;&gt;patch&lt;/a&gt; by &lt;a href=&quot;https://github.com/fduraffourg&quot;&gt;Florian Duraffourg&lt;/a&gt;, he use a hashmap to replace a slow list search.&lt;/p&gt;

&lt;h2 id=&quot;looking-forward&quot;&gt;Looking Forward&lt;/h2&gt;

&lt;p&gt;In the near future, we’ll focus on improving the framework’s stability to support Servo’s performance optimization endeavor. We’ll also work closely with the Treeherder team to expand the flexibility of Treeherder and Perfherder to support more performance frameworks.&lt;/p&gt;

&lt;p&gt;If you are interested in the framework, you can find open bugs &lt;a href=&quot;https://github.com/shinglyu/servo-perf/issues&quot;&gt;here&lt;/a&gt;, or join the discussion in the &lt;a href=&quot;https://github.com/servo/servo/issues/10452&quot;&gt;tracking bug&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks &lt;a href=&quot;https://github.com/wlach&quot;&gt;William Lachance&lt;/a&gt; for his help on the Treeherder and Perfherder stuff, and helped me a lot in setting it up on Treeherder staging server. And thanks &lt;a href=&quot;https://github.com/larsbergstrom&quot;&gt;Lars Bergstrom&lt;/a&gt; and &lt;a href=&quot;https://github.com/metajack&quot;&gt;Jack Moffit&lt;/a&gt; for their advice throughout the planning process. And thanks &lt;a href=&quot;https://github.com/autrilla&quot;&gt;Adrian Utrilla&lt;/a&gt; for contributing many good features to this project.&lt;/p&gt;
</description>
        <pubDate>Mon, 18 Jul 2016 02:00:00 +0000</pubDate>
        <link>https://shinglyu.com/web/2016/07/18/identify-performance_regression-in-servo.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2016/07/18/identify-performance_regression-in-servo.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Show Firefox Bookmark Toolbar in Fullscreen Mode</title>
        <description>&lt;p&gt;By default, the bookmark toolbar is hidden when Firefox goes into fullscreen mode. It’s quite annoying because I use the bookmark toolbar a lot. And since I use &lt;a href=&quot;https://i3wm.org&quot;&gt;i3 window manager&lt;/a&gt;, I also use the fullscreen mode very often to avoid resizing the window. After some googling I found this &lt;a href=&quot;https://support.mozilla.org/zh-TW/questions/1039009&quot;&gt;quick solution&lt;/a&gt; on SUMO (Firefox commuity is awesome!).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/bookmark_bar/before.png&quot; alt=&quot;before&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The idea is that the Firefox &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Chrome&quot;&gt;chrome&lt;/a&gt; (not to be confused with the Google Chrome browser) is defined using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL&quot;&gt;XUL&lt;/a&gt;. You can adjust its styling using CSS. The user defined chrome CSS is located in your &lt;a href=&quot;https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data&quot;&gt;Firefox profile&lt;/a&gt;. Here is how you do it:&lt;/p&gt;

&lt;!--more--&gt;

&lt;ul&gt;
  &lt;li&gt;Open your Firefox &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL&quot;&gt;profile folder&lt;/a&gt;, which is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.mozilla/firefox/&amp;lt;hash&amp;gt;.&amp;lt;profile_name&amp;gt;&lt;/code&gt; on Linux. If you can’t find it, you can open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;about:support&lt;/code&gt; in your Firefox. and click the “Open Directory” button in the “Profile Directory” field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/bookmark_bar/about_support.png&quot; alt=&quot;before&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create a folder named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome&lt;/code&gt; if it doesn’t exist yet.&lt;/li&gt;
  &lt;li&gt;Create a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userChrome.css&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome&lt;/code&gt; folder, copy the following content into it and save.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code&gt;
@namespace url(&quot;http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul&quot;); /* only needed once */

/* full screen toolbars */
#navigator-toolbox[inFullscreen] toolbar:not([collapsed=&quot;true&quot;]) {
   visibility:visible!important;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
  &lt;li&gt;Restart your Firefox and Voila!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/bookmark_bar/after.png&quot; alt=&quot;before&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 20 Jun 2016 11:05:00 +0000</pubDate>
        <link>https://shinglyu.com/web/2016/06/20/firefox_bookmark_toolbar_in_fullscreen.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2016/06/20/firefox_bookmark_toolbar_in_fullscreen.html</guid>
      </item>
      
    
    
    
    
    
    
    
      
      <item>
        <title>Rust Code Completion with YouCompleteMe</title>
        <description>&lt;p&gt;Every programmer knows that code completion can give you a productivity boost. Without code completion, you’ll have to read the documentation (or StackOverflow?) every time you forget what to type next. Since I’m a vim user and I worked with &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; very often, I’m glad to know &lt;a href=&quot;http://valloric.github.io/YouCompleteMe/&quot;&gt;YouCompleteMe&lt;/a&gt; (YCM), a powerful code completion plug-in for vim, officially supports Rust code completion (see this &lt;a href=&quot;http://blog.jwilm.io/youcompleteme-rust/&quot;&gt;blog post&lt;/a&gt;). I’ll walk you through the process of installing YCM on Ubuntu/Linux Mint in this post.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;prerequisite&quot;&gt;Prerequisite&lt;/h2&gt;
&lt;p&gt;You’ll need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;vim (obviously!)&lt;/li&gt;
  &lt;li&gt;git (Vundle needs it)&lt;/li&gt;
  &lt;li&gt;latest stable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rustc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo&lt;/code&gt; (YCM needs to compile the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;racerd&lt;/code&gt; completion server, follow this &lt;a href=&quot;http://doc.rust-lang.org/book/getting-started.html#installing-rust&quot;&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Python 2&lt;/li&gt;
  &lt;li&gt;Install the required packages for YCM: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt-get install build-essential cmake python-dev python3-dev&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use other OS, you can find the installation guide &lt;a href=&quot;https://github.com/Valloric/YouCompleteMe#installation&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;First, we’ll install YouCompleteMe through Vundle. YCM is not a simple vimscript or python based vim plug-in, there are compiled components so you’ll have to compile it when you first install it, and re-compile it every time you update. &lt;a href=&quot;https://github.com/VundleVim/Vundle.vim&quot;&gt;Vundle&lt;/a&gt; is a package manager for vim; It allows you to specify the plug-ins you want in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; file and it will handle the install/uninstall for you. Vundle can be installed easily by following the official &lt;a href=&quot;https://github.com/VundleVim/Vundle.vim#quick-start&quot;&gt;quick start guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After you installed Vundle, you can add the following line to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-viml&quot; data-lang=&quot;viml&quot;&gt;Plugin &lt;span class=&quot;s1&quot;&gt;&apos;Valloric/YouCompleteMe&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:PluginInstall&lt;/code&gt; in vim (in normal mode). Vundle should download and install YCM into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vim/bundle/YouCompleteMe&lt;/code&gt; folder. YCM is quite big in size, so if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:PluginInstall&lt;/code&gt; step may takes a long time based on your network connection, be patient and wait for it to finish.  If you already have an old version of YCM installed, you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:PluginUpdate&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Now, let’s compile YCM and include the Rust support:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/.vim/bundle/YouCompleteMe
./install.py &lt;span class=&quot;nt&quot;&gt;--racer-completer&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;install.py&lt;/code&gt; command It will take some time to compile. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--racer-completer&lt;/code&gt; argument tells the YCM installer to download &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;racerd&lt;/code&gt; via &lt;a href=&quot;http://doc.crates.io/index.html&quot;&gt;Cargo&lt;/a&gt; and compile it for you.&lt;/p&gt;

&lt;p&gt;If you ever wonder why it’s called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--racer-completer&lt;/code&gt;, it’s because YCM relies on &lt;a href=&quot;https://github.com/jwilm/racerd&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;racerd&lt;/code&gt;&lt;/a&gt; for Rust semantic completion. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;racerd&lt;/code&gt; is a JSON/HTTP server powered by &lt;a href=&quot;&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;racer&lt;/code&gt;&lt;/a&gt;, a Rust code completion tool. (You can install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;racer&lt;/code&gt; directly and use it with vim plugins like &lt;a href=&quot;&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vim-racer&lt;/code&gt;&lt;/a&gt;, but it’s not as convenient as YCM.) When you type your Rust code in vim, YCM will communicate with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;racerd&lt;/code&gt; to get the code completion suggestions.&lt;/p&gt;

&lt;p&gt;For the Rust completer to work, you’ll also need a local copy of the Rust source code. You can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone&lt;/code&gt; the Rust source code from &lt;a href=&quot;https://github.com/rust-lang/rust&quot;&gt;GitHub&lt;/a&gt; or &lt;a href=&quot;https://www.rust-lang.org/downloads.html&quot;&gt;download the tarball&lt;/a&gt;. Choose the version you’ll be writing Rust code in. Then you’ll need to point to the source code in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt; like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-viml&quot; data-lang=&quot;viml&quot;&gt;&lt;span class=&quot;c&quot;&gt;&quot; In this example, the rust source code zip has been extracted to&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; /usr/local/rust/rustc-1.8.0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;g:ycm_rust_src_path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/usr/local/rust/rustc-1.8.0/src&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we are all set! Next time you opened a Rust file, YCM will start to work.&lt;/p&gt;

&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;

&lt;p&gt;YCM automatically kicks in when  you type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;. Here are some example:&lt;/p&gt;

&lt;p&gt;Code completion for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/rust_ycm/use.png&quot; alt=&quot;use&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Code completion for functions:
&lt;img src=&quot;/blog_assets/rust_ycm/string.png&quot; alt=&quot;string&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As you type more, the list of suggestions will be filtered automatically:
&lt;img src=&quot;/blog_assets/rust_ycm/string_narrow.png&quot; alt=&quot;string_narrow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It can also help you “jump” to the definition of a variable, function, struct, etc. Simply locate your cursor on anything (in normal mode) and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:YcmCompleter GoTo&lt;/code&gt; to jump.&lt;/p&gt;

&lt;p&gt;Let’s say we want to know the definition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline_mode_assign_inline_sizes()&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/rust_ycm/jump_before.png&quot; alt=&quot;before jump&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Boom! It jumps to the function definition:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/rust_ycm/jump_after.png&quot; alt=&quot;after jump&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can map the verbose command to some hotkey, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\]&lt;/code&gt; (backslash followed by right square bracket),  add the following line to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vimrc&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-viml&quot; data-lang=&quot;viml&quot;&gt;nnoremap &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;Leader&lt;span class=&quot;p&quot;&gt;&amp;gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;YcmCompleter GoTo&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;CR&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</description>
        <pubDate>Fri, 13 May 2016 15:00:00 +0000</pubDate>
        <link>https://shinglyu.com/productivity/2016/05/13/rust-completion_with_ycm.html</link>
        <guid isPermaLink="true">https://shinglyu.com/productivity/2016/05/13/rust-completion_with_ycm.html</guid>
      </item>
      
    
    
    
    
    
      
      <item>
        <title>Visualizing Performance Data on Perfherder</title>
        <description>&lt;p&gt;At Mozilla, we use &lt;a href=&quot;https://treeherder.mozilla.org/perf.html#/graphs?series=[mozilla-inbound,954c0eef0296505ed478961111939345b4e83960,1]&quot;&gt;Perfherder&lt;/a&gt; to visualize our performance testing data. Perfherder is a dashboard for monitoring and analyzing performance metrics, which can include page load time, JavaScript benchmark score, build time, installer size, etc. Perfherder allow you to visualize the data as a line chart, compare performance metrics between versions and automatically create &lt;a href=&quot;https://treeherder.mozilla.org/perf.html#/alerts&quot;&gt;alerts&lt;/a&gt; for sudden change in performance metrics.&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;Recently I was doing &lt;a href=&quot;https://github.com/servo/servo/issues/10452&quot;&gt;page load performance testing&lt;/a&gt; for &lt;a href=&quot;https://servo.org/&quot;&gt;Servo&lt;/a&gt;, our goal is to test how fast Servo can load real web pages, comparing to other browser engines. I build a test runner in Python from scratch. But for the data visualization and analysis, I want to levarage existing solutions. I tried &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Archive/Firefox_OS/Automated_testing/Raptor&quot;&gt;Raptor&lt;/a&gt;, which is a dashboard we used for the B2G OS project. It was quite neat, using a &lt;a href=&quot;https://influxdata.com/&quot;&gt;influxDB&lt;/a&gt; backend and &lt;a href=&quot;http://grafana.org/&quot;&gt;Grafana&lt;/a&gt; frontend. But since the B2G OS project is sunsetting, the Raptor tool is no longer under active development. So I decided to use Perfherder, which is used heavily by the Platform (Gecko) team.&lt;/p&gt;

&lt;h2 id=&quot;basic-concepts&quot;&gt;Basic Concepts&lt;/h2&gt;

&lt;p&gt;Before we dive into the code, we should understand some basic concepts about Perfherder in general. Perfherder is part of the Treeherder project, which is a dashboard for test results. For the Gecko project, the test results are from  build jobs and test jobs running on buildbot (deprecated) and &lt;a href=&quot;https://docs.taskcluster.net/&quot;&gt;TaskCluster&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can see the architecture of Treeherder in this &lt;a href=&quot;https://treeherder.readthedocs.io/list_of_services.html&quot;&gt;diagram&lt;/a&gt;. It’s OK if you can’t understand everything at one glance, we won’t need to understand them all to use Perfherder. For user who wants to submit test results, there is a web API server that accepts the test result data (as JSON), and Treeherder stores it in a MySQL database. When the user opens &lt;a href=&quot;https://treeherder.mozilla.org&quot;&gt;https://treeherder.mozilla.org&lt;/a&gt;, the web server renders the dashboard and the Perfherder graph from the data in the database.&lt;/p&gt;

&lt;p&gt;Treeherder/Perfherder is for build results and test results, so it introduced the concept of a &lt;em&gt;Resultset&lt;/em&gt;. A Resultset is a container for all the build results and test results triggered by a certain GitHub pull request or Mercurial push. A Resultset should contain a &lt;em&gt;revision&lt;/em&gt;, which is the commit SHA for the top-most commit of the push. Since a push may contain multiple commits, the rest of the commits goes into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;revisions&lt;/code&gt; list, which we shall see in the following code examples. And you can optionally add the commit’s author, timestamp and other metadata to the Resultset.&lt;/p&gt;

&lt;p&gt;After the Resultset was created, we can add test results to it as a &lt;em&gt;Job collection&lt;/em&gt;. The revision in the job collection should match the one in the Resultset, otherwise the result will not be visible on Treeherder. The job collection contains metadata associated with a test, like its name, the platform it runs on and the time it takes, etc. And most importantly, the job collection can contain &lt;em&gt;artifacts&lt;/em&gt;, which can be test result data, logs, and any other artifact the test produced. For submitting data to Perfherder, we need to provide a &lt;em&gt;performance artifact&lt;/em&gt;, which is an artifact with a specific format that Perherder can parse. We’ll show you the format in the following example.&lt;/p&gt;

&lt;p&gt;Treeherder provides a REST API and a &lt;a href=&quot;https://pypi.python.org/pypi/treeherder-client&quot;&gt;python client&lt;/a&gt;, so you can easily submit data to it programmatically.&lt;/p&gt;

&lt;p&gt;So to sum up, a general workflow will be:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;When someone opens a new push to the GitHub/HG repository, build and test it.&lt;/li&gt;
  &lt;li&gt;Collect the build metadata (commit hash) and test result, format them as Resultset and performance artifacts.&lt;/li&gt;
  &lt;li&gt;Submit the Resultset.&lt;/li&gt;
  &lt;li&gt;Submit a Job collection containing the performance artifact to the said Resultset.&lt;/li&gt;
  &lt;li&gt;Go to Treeherder/Perfherder to see the results.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;setting-up-a-local-treeherder-server&quot;&gt;Setting up a local Treeherder server&lt;/h2&gt;
&lt;p&gt;If you are submitting new kinds of data to Treeherder, you may want to host a Treeherder server on your own computer for testing. If you already have access to the stage or production server, you can skip this section.&lt;/p&gt;

&lt;p&gt;Treeherder is open source (of course), and it provides a &lt;a href=&quot;https://github.com/mozilla/treeherder/blob/master/Vagrantfile&quot;&gt;Vagrant&lt;/a&gt; configuration for you to install it effortlessly. There is an excellent &lt;a href=&quot;https://treeherder.readthedocs.io/installation.html&quot;&gt;documentation&lt;/a&gt; you can follow, so we won’t go into detail in this post.&lt;/p&gt;

&lt;p&gt;After you installed the server as a Vagrant virtual machine (VM), you need to &lt;a href=&quot;https://treeherder.readthedocs.io/common_tasks.html#generating-and-using-credentials-on-a-local-testing-instance&quot;&gt;generate an API credential&lt;/a&gt; so you can push data to it through the Python client. After you are confident in your data submission process, you can &lt;a href=&quot;https://treeherder.readthedocs.io/common_tasks.html#generating-and-using-credentials-on-treeherder-stage-or-production&quot;&gt;request for access to the stage or production server&lt;/a&gt;. Notice that before you generate the credential, you should at least login to your Treeherder server once from the Web UI, otherwise the create credential script will complain that the user does not exist.&lt;/p&gt;

&lt;p&gt;If you want to create a new repository on Treeherder, just like we do for Servo, you can follow these &lt;a href=&quot;https://treeherder.readthedocs.io/common_tasks.html#add-a-new-repository&quot;&gt;instructions&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;installing-the-treeherder-client&quot;&gt;Installing the Treeherder Client&lt;/h2&gt;
&lt;p&gt;Installing the Python client is easy. If you use virtualenv, you can simply run:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;virtualenv venv
&lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;venv/bin/activate
pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;treeherder-client&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, you can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import thclient&lt;/code&gt; in your python script to use it. It provides endpoints for Resultset and Job collections. You can read more in the &lt;a href=&quot;https://treeherder.readthedocs.io/submitting_data.html&quot;&gt;official documentation&lt;/a&gt;, but be warned, the example code has errors. We’ll show you a modified example instead.&lt;/p&gt;

&lt;h2 id=&quot;submitting-a-resultset&quot;&gt;Submitting a Resultset&lt;/h2&gt;
&lt;p&gt;First, we need to submit our build metadata as a Resultset. The code we use for production can be found &lt;a href=&quot;https://github.com/shinglyu/servo-perf/blob/stage/submit_to_perfherder.py#L71..L119&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The data should look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;# The top-most revision in the list of commits for a push.
&lt;/span&gt;                &lt;span class=&quot;s&quot;&gt;&apos;revision&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;ab12d8098fcc0517b64643d25683e2e15e665410&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Commit hash
&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;author&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;bos-servo &amp;lt;bors@allizom.com&amp;gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Commit author
&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;push_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1462525798&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Unix epoch time
&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;push&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

                &lt;span class=&quot;c1&quot;&gt;# a list of revisions associated with the resultset. There should be
&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;# at least one.
&lt;/span&gt;                &lt;span class=&quot;s&quot;&gt;&apos;revisions&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;s&quot;&gt;&apos;comment&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Auto merge of #11033 - mbrubeck:max-log, r=metajack&apos;&lt;/span&gt;
                        &lt;span class=&quot;s&quot;&gt;&apos;revision&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;ab12d8098fcc0517b64643d25683e2e15e665410&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Commit hash
&lt;/span&gt;                        &lt;span class=&quot;s&quot;&gt;&apos;author&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;bos-servo &amp;lt;bors@allizom.com&amp;gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Commit author
&lt;/span&gt;                        &lt;span class=&quot;s&quot;&gt;&apos;repository&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;servo&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, we fill in the data into a Resultset object for submission:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;thclient&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TreeherderClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TreeherderResultSetCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create a Resultset collection
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trsc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TreeherderResultSetCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Fill-in the fields
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Get a Resultset instance
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;trs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trsc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_resultset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;trs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_push_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;push_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;trs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_revision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;revision&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;trs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_author&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;author&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# trs.add_type(data[&apos;type&apos;]) # Optional
&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;revisions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rev&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;revisions&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_revision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;tr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_revision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;revision&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_author&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;author&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_comment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;comment&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;repository&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;revisions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;trs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_revisions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;revisions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;trsc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Finally, we create a Treeherder client and submit the data (through an HTTP(S) POST request):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Use the client id and secure you created before.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TreeherderClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;https&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Use &apos;http&apos; if running locally
&lt;/span&gt;                          &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;local.treeherder.mozilla.org&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;n&quot;&gt;client_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;servo&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;xxxxxxxxxxxxxxxx&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 

&lt;span class=&quot;n&quot;&gt;project_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;servo&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# data structure validation is automatically performed here, if validation
# fails a TreeherderClientError is raised
&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post_collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trsc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is how it looks like on Treeherder:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/perfherder/resultset.png&quot; alt=&quot;resultset&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you use a GitHub webhook, you should be able to get the commit metadata directly. But if you are running the build and test locally, you can use &lt;a href=&quot;https://github.com/shinglyu/servo-perf/blob/master/git_log_to_json.sh&quot;&gt;this script&lt;/a&gt; to dump the latest commit as JSON, so your Python script can load it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json.load()&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;submitting-a-performance-artifact&quot;&gt;Submitting a Performance Artifact&lt;/h2&gt;
&lt;p&gt;After the Resultset was created, we can submit a Job collection containing the performance artifact:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&apos;project&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;servo&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&apos;revision&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;ab12d8098fcc0517b64643d25683e2e15e665410&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Commit hash
&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;job_guid&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job_guid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# This is a job ID from your test runner, use it to identify the test. 
&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;product_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;servo&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;s&quot;&gt;&apos;reason&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;scheduler&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;who&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Servo&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;s&quot;&gt;&apos;desc&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Servo Page Load Time Tests&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Test description
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Servo Page Load Time&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;# Test name
&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# The symbol representing the job displayed in
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# treeherder.allizom.org
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;job_symbol&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;PL&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;# The symbol representing the job group 
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;group_symbol&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;SP&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;group_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Servo Perf&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;# If your test runner tracks the start and end time, you can submit it here
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;submit_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1462525798&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;start_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1462525798&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;s&quot;&gt;&apos;end_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1462525798&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            
            &lt;span class=&quot;c1&quot;&gt;# You can submit states like &apos;pending&apos; so user can see the status of the test
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;state&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;completed&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;result&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;success&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;s&quot;&gt;&apos;machine&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;local-machine&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;build_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;linux64&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;os_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;linux&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;architecture&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;x86_64&apos;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&apos;machine_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;linux64&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;os_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;linux&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&apos;architecture&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;x86_64&apos;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

            &lt;span class=&quot;s&quot;&gt;&apos;option_collection&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;opt&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;# jobs can belong to different tiers
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# setting the tier here will determine which tier the job
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# belongs to.  However, if a job is set as Tier of 1, but
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# belongs to the Tier 2 profile on the server, it will still
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# be saved as Tier 2.
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;tier&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;# the ``name`` of the log can be the default of &quot;buildbot_text&quot;
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# however, you can use a custom name.  See below.
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# You can upload your test log to some public URL and point to it
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;log_references&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&apos;url&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;TBD&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;test log&apos;&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# The artifact can contain any kind of structured data
&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# associated with a test.
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;artifacts&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&apos;type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;performance_data&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;# &apos;job_guid&apos;: job_guid,
&lt;/span&gt;                    &lt;span class=&quot;s&quot;&gt;&apos;blob&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perf_data&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Will be covered later
&lt;/span&gt;                &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&apos;type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;Job Info&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;#&apos;job_guid&apos;: job_guid,
&lt;/span&gt;                    &lt;span class=&quot;s&quot;&gt;&quot;blob&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;s&quot;&gt;&quot;job_details&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                                &lt;span class=&quot;s&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://www.github.com/servo/servo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                &lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;website&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                &lt;span class=&quot;s&quot;&gt;&quot;content_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;link&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                &lt;span class=&quot;s&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Source code&quot;&lt;/span&gt;
                            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# List of job guids that were coalesced to this job
&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;&apos;coalesced&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After you submit this data (see the submission code below), you’ll see a test result marked as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PL&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SP&lt;/code&gt; group:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/perfherder/result_page.png&quot; alt=&quot;result_page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the data, notice that we didn’t define the &lt;em&gt;job_guid&lt;/em&gt;, this it whatever unique ID you used in your test runner to identify a test. We didn’t have this in our simple test runner, so we just randomly generate a string for it.&lt;/p&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;artifacts&lt;/code&gt; section, we submitted two types of artifacts, first is the performance artifact, which we’ll explain right away. The second is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_details&lt;/code&gt; artifact, which will show up in the Treeherder UI like so:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/perfherder/job_info.png&quot; alt=&quot;job_info&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The performance artifact &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf_data&lt;/code&gt; should look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&quot;performance_data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# You need to mark the framework as &quot;talos&quot; so 
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Treeherder will generate a &quot;Performance&quot; tab
&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;&quot;framework&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;talos&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; 
        &lt;span class=&quot;s&quot;&gt;&quot;suites&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;domComplete&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3741.657386773941&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;subtests&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;about:blank&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;163.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;14000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here we submit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;domComplete&lt;/code&gt; time for loading the pages. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt; is a geometric mean of all the subtests’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;domComplete&lt;/code&gt; time. You can use other ways to summarize the results from all the subtests. Be careful not to submit data that is over 80 characters long, Threeherder has an &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1269629#c1&quot;&gt;undocumented limit&lt;/a&gt; for that.&lt;/p&gt;

&lt;strike&gt;By assigning the framework as `talos` (a performance suite used in Gecko)&lt;/strike&gt;
&lt;p&gt;, the Treeherder UI will generate a “Performance” tab for us, from which you can find the link to the Perfherder view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;: If you have a new perofrmance test framework, and want Treeherder to generate the “Performance” tab for you, you should open a bug for that. Here is an &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1271472&quot;&gt;example&lt;/a&gt;. After you add your framework to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;treehderder/model/fixtures/performance_framework.json&lt;/code&gt;, you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;framework&quot;: {&quot;name&quot;: &quot;your_framwork_name&quot;}&lt;/code&gt; in your performance artifact.
&lt;img src=&quot;/blog_assets/perfherder/performance_tab.png&quot; alt=&quot;performance_tab&quot; /&gt;&lt;/p&gt;

&lt;p&gt;By clicking on the test result number 9607, you’ll be directed to the graph:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/perfherder/graph_summary.png&quot; alt=&quot;graph_summary&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you click on the “+ Add more test data” button, you can choose to show all subtest data:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog_assets/perfherder/graph_subtests.png&quot; alt=&quot;graph_subtests&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And this is the code for actually submitting the data to Treeherder; we leave it to the end because it’s similar to the previous one. This is of course a simplified version, to see the real code I run in production, check &lt;a href=&quot;https://github.com/shinglyu/servo-perf/blob/stage/submit_to_perfherder.py#L121..L277&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tjc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TreeherderJobCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tjc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_revision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;revision&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;project&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_coalesced_guid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;coalesced&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_job_guid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job_guid&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_job_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_job_symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job_symbol&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_group_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;group_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_group_symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;group_symbol&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;desc&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_product_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;product_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;state&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;result&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;reason&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_who&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;who&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_tier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;tier&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_submit_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;submit_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_start_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;start_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_end_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;end_timestamp&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;machine&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_build_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;build_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;os_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;build_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;build_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;architecture&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_machine_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;machine_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;os_name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;machine_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;machine_platform&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;architecture&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_option_collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;option_collection&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# for log_reference in data[&apos;job&apos;][&apos;log_references&apos;]:
&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;#    tj.add_log_reference( &apos;buildbot_text&apos;, log_reference[&apos;url&apos;])
&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# data[&apos;artifact&apos;] is a list of artifacts
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifact_data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;job&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;artifacts&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_artifact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;artifact_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;artifact_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;type&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;artifact_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;blob&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tjc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post_collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tjc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;its-your-turn&quot;&gt;It’s Your Turn&lt;/h2&gt;

&lt;p&gt;This is how easy it is to submit data to Perfherder. If you have any project relevant to Mozilla, and want to show your data on Perfherder, you can &lt;a href=&quot;https://treeherder.readthedocs.io/common_tasks.html#generating-and-using-credentials-on-treeherder-stage-or-production&quot;&gt;request for access to the stage or production server&lt;/a&gt;. Since everything is open source, you can also consider hosting your own Treeherder/Perfherder instance. If you are interested in Treeherder and Perfherder, you can read their &lt;a href=&quot;https://treeherder.readthedocs.io/&quot;&gt;official documentation&lt;/a&gt;, &lt;a href=&quot;https://bugzilla.mozilla.org/enter_bug.cgi?product=Tree+Management&amp;amp;component=Treeherder&quot;&gt;report issues&lt;/a&gt;, or contribute code to their &lt;a href=&quot;https://github.com/mozilla/treeherder&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are interested in Servo’s performance testing, you can follow this &lt;a href=&quot;https://github.com/servo/servo/issues/10452&quot;&gt;GitHub Issue&lt;/a&gt;. Submit issues and contribute pull requests in the &lt;a href=&quot;https://github.com/shinglyu/servo-perf&quot;&gt;servo-perf&lt;/a&gt; repository. (Although we might soon merge the code back to the Servo main repository.)&lt;/p&gt;

&lt;p&gt;Special thanks for William Lachance for teaching me how to use Perfherder and opening accounts for me.&lt;/p&gt;
</description>
        <pubDate>Sat, 07 May 2016 13:20:00 +0000</pubDate>
        <link>https://shinglyu.com/web/2016/05/07/visualizing_performance_data_on_perfherder.html</link>
        <guid isPermaLink="true">https://shinglyu.com/web/2016/05/07/visualizing_performance_data_on_perfherder.html</guid>
      </item>
      
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
  </channel>
</rss>
