Blog

  • Testing Randomness with Deterministic Tests

    I had an interesting learning experience with a live coding interview recently.

    I documented my complete experience in a GitHub repo here. This post here is a copy for posterity and discoverability.

    The question

    This is what I remember from the document shown to me during the video call.

    The task was to implement a function called randomWord that takes an array of words and returns a random word that satisfies the following rules:

    1. The first letter of the random word must be the first letter of one of the words input.
    2. The last letter of the random word must be the last letter of one of the words input.
    3. Each pair of adjacent letters in the random word must exist as adjacent letters in the words input. (This is not applicable when the random word generated is only one letter long.)

    Example:

    Assume that the words input is ["apple", "banana", "cabbage"]. The following words are valid random words:

    • "abage":
      • First letter: a (from apple)
      • Last letter: e (from apple)
      • Adjacent letters: abbaagge (all exist in the input words)

    The following words are also valid random words that satisfy the three rules above:

    • "banage"
    • "bage"
    • "a"

    During the interview: the challenge and the confusion

    After I read the question and the given examples, I was feeling a little confused.

    The main challenge for me is that the function is supposed to generate a random string. The randomness nature in a coding test like this is quite new to me. My past experience with interviews and coding challenges (e.g. see my solutions for LeetCodeCodility and HackerRank) are mostly deterministic – given the same input, the function must always return the same output. In these challenges, before I write my solution, I will use the TDD approach to write some tests first based on the given examples and requirements. However, for this interview question, since the function should return a random string, what should be the expected correct value in the tests? And what should be the expected wrong value, which is not mentioned in the example?

    My mind also got confused with the Rule 3 and the "a" example. There wasn’t a clear explanation in the document on why "a" is a valid word and how it is only one-letter long. I had to ask my interviewer Kevin for more explanation and had to code and try my way forward with uncertainty. My Garmin watch was telling me that my heart rate was at 120bpm. Yup, interview pressure is real.

    I looked at the words input and the three rules again. Since the adjacent letters are based on one letter and the letter next to it, I thought that this is a linked list question. Using ["apple", "banana", "cabbage"] returning "abage" as an example, this means that there are three linked lists, and we need to return a new linked list a -> b -> a -> g -> e. However, the problem with this thinking is: for a valid word like "abanabanabanage", how do we point back to a character that has already passed in the linked list traversal?

    Since my mind is blocked and can’t proceed further, I asked Kevin for some hints. He mentioned that since we want to go from one letter to the next possible letters, we would want to have a map of letter pairs. This idea is easy and not new to me, so I proceeded to code it out. I also created new variables to keep track of the starting letters and ending letters, so that it can help us in validating rule 1 and rule 2 later.

    After I build the above mapping, it still wasn’t quite clear to me on how I should build the valid word. Kevin mentioned using a random number generator to randomly select a starting letter, and then randomly select the next possible letter, and so on. It is at this point that it became clear to me on how the function should work.

    However, because I was really nervous, I did quite some minor mistakes in my code. The time ran out and the code did not work as expected. Kevin mentioned that the code should be almost there, and he also gave me some suggestions on how he would write the code, e.g. we do not need the ending letters variable and we can simply reuse the pairs mapping by using undefined to indicate that a letter is an ending letter. I asked for his feedback on me, and he mentioned he likes the way I communicate throughout the process, and the way I asked for help when I was blocked. I submitted the code as-is to the hiring team. The code is available in commit 2cf20df.

    After the interview: the enlightenment

    Even though the interview was over, my mind was still thinking about the problem. When I have a problem, I want to solve it. After I calmed myself down a little from the interview pressure, I looked into my solution again, trying to figure out why my solution did not work. It turned out that there were two typo mistakes in my code. After I fixed the two typos (see commit 2dc8890), it worked.

    I also spent some time thinking about Kevin’s suggestion, and I implemented it in commit f6f0bd2. It makes the solution simpler and more elegant. I sent a copy of the code – which is working now – to the hiring team again, two hours after the end of the live coding interview.

    Even though I have a working solution, I am still feeling restless. Earlier in this article, I mentioned that the main challenge for me is the randomness nature of this coding question. How do we test the correctness of the solution? In the test file, I was just doing console.log('result:', result) without doing any assertion. That didn’t sit well with me.

    I gave some thoughts about this, and I figured a way to turn randomness into deterministic tests.

    When we call randomWord(words) function, it will generate and return a random word. We need another function to check whether the random word satisfies the three rules. The first idea of the function is something like this:

    checkRandomWordIsValid(words, generatedWord)
    
    // Example usage:
    checkRandomWordIsValid(["apple", "banana", "cabbage"], "abage") // returns true.
    checkRandomWordIsValid(["apple", "banana", "cabbage"], "bage") // returns true.
    checkRandomWordIsValid(["apple", "banana", "cabbage"], "a") // returns true.
    
    checkRandomWordIsValid(["apple", "banana", "cabbage"], "ge") // returns false: "g" is not a starting letter.
    checkRandomWordIsValid(["apple", "banana", "cabbage"], "ban") // returns false: "n" is not an ending letter.
    checkRandomWordIsValid(["apple", "banana", "cabbage"], "cabnage") // returns false: "bn" is not a valid adjacent letters.
    
    // In tests:
    expect(checkRandomWordIsValid(["apple", "banana", "cabbage"], "abage")).toBe(true)
    
    

    With the above, we have deterministics tests! Also, by having this function, internally we would essentially have the same logic for generating letter pairs (this is now done in commit [1681d0e]https://github.com/ecgan/gdnt-interview/commit/1681d0ef5b9572a5e58559f9ee18209b00ac6721) with a new function called convertToLettersMap). On hindsight, I felt that if I were to think of creating deterministic tests in the first place, it would definitely help guide me in thinking and creating a working solution for the question.

    However, this is just one part of the story. We have a new function checkRandomWordIsValid and we have tests for it, but how do we test the main randomWord function in question?

    The answer lies in calling randomWord function many times, and for each answer that it generates, we call checkRandomWordIsValid function, and it must return true. That’s it! We don’t need to check for false value since randomWord should never return an invalid word.

    Here’s an example:

    
    // inside a for-loop that iterates for 1000 times:
    const word = randomWord(["apple", "banana", "cabbage"])
    const isValid = checkRandomWordIsValid(["apple", "banana", "cabbage"], word)
    expect(isValid).toBe(true)
    

    Since we will be calling checkRandomWordIsValid function many times, instead of rebuilding the letter pairs internally every time it is called, we can come up with a RandomWordChecker class, build and store the letter mapping when we instantiate a new RandomWordChecker object, and reuse the same object by calling its isValid(word) method.

    The test for randomWord function would then become like this:

    const words = ["apple", "banana", "cabbage"]
    const checker = new RandomWordChecker(words)
    
    for (let i = 0; i < 1000; i++) {
        const result = randomWord(words)
        expect(checker.isValid(result)).toBe(true)
    }
    
    

    So, to recap, at this point, for this live coding interview question:

    • We have a working solution for randomWord.
    • We came up with a RandomWordChecker, and we have tests for it, to make sure that a word satisfies the three rules in the question.
    • randomWord and RandomWordChecker depends on convertToLettersMap function to build a map of letter pairs. The function is indirectly covered by the tests for RandomWordChecker.
    • We have tests for randomWord which make use of the RandomWordChecker, and we call it many times to make sure that every random word generated is valid.
    • We have 100% test coverage.

    That’s it. All the problems and challenges are solved. Even though I did not do really well as I hoped for during the live coding interview, this is an interesting learning experience, and I’m glad with what I came up with above.

  • Trust and Verify Your AI Coding Agent

    I was working on a project recently using VS Code with GitHub Copilot. I used the agent mode with Claude Sonnet 4 model and I had a few imperfect experiences.

    In one instance, the agent mentioned that it reduced the time complexity from O(n2) to O(n), but it wasn’t O(n2) to begin with.

    In another instance, the agent created code that is not being used at all:

    In summary, we should trust and verify the output of our coding agent, at least for now.

  • WordCamp Malaysia 2024 recap

    Last month, I attended WordCamp Malaysia 2024, and also helped to staff our Jetpack booth.

    Overall it was a good and enjoyable experience.

    On the first day, I attended the contributor day event. I chose to be in the Core group, thinking to contribute issues and fixes to the Gutenberg repository. Along the way, I had some conversations with first-time contributors, and I managed to offer my advices and helped them out.

    It is interesting to see how far I’ve come. In WordCamp Asia (Bangkok) in February 2023, I knew nothing about contributor day, and I knew little about WordPress  core development; and now one year later, I am helping other people who are new to WordPress development in contributor day.

    Me in action during contributor day.
    Everyone on the contributor day. Find me.

    After contributor day, my colleagues and I attended the sponsors/speakers appreciation dinner in Simply Ribs restaurant (Google Maps) where we had the chance to network with the organizers, sponsors, and speakers. The food was really good, though the space is quite limited for the amount of people we had. We got to talk to people about our products like WordPress.com, WooCommerce, Jetpack, Automattic for Agencies etc. It is always food to get direct first-hand feedback from them and understand their pain points in using our products.

    On the second day, I helped out my colleagues in staffing the Jetpack booth. Even though I am not in the Jetpack division and I don’t work directly with Jetpack, my experiences in staffing Jetpack booth in past WordCamp events and working with Jetpack connection in WooCommerce extensions are helpful. We got to promote our Jetpack products to people who are new to them, and we got to listen to feedback and pain points from people who have used them.

    Must-have “Automatticians, assemble!” photo in front of the WordCamp Malaysia signboard.

    I also used the opportunity to attend some of the speaker sessions, especially the ones that are related to AI, coding and development. If you like to watch them, you can find them in WordPress.tv.

    Photo at the end of conference event day.

    After the event day, I attended an unofficial dinner meetup organized by Appfromlab to connect with other plugin builders. I got to meet a bunch of interesting people and learn from their experiences, and also helped to answer their questions related to WooCommerce and Automattic.

    Unofficial dinner meetup after the event.

    Throughout the two days, I also used the opportunity to talk about our WooCommerce projects, specifically our existing Analytics feature in WooCommerce core and also our new WooCommerce Analytics plugin that we are working on.

    If you like to see more photos, you can take a look at the public shared albums in Google Photos:

  • My first WordPress core ticket

    My first WordPress core ticket

    I was doing some testing for our WooCommerce Points and Rewards plugin, and I noticed that there is an issue in the privacy policy page in WordPress core.

    The privacy policy guide accordion panels for plugins with non-English characters do not work. This can happen when the user profile language is set to a different language and some plugin names would get translated. When we click on the accordion, there is a JavaScript error in the browser console that says Uncaught Error: Syntax error, unrecognized expression: #privacy-settings-accordion-block-%e9%bb%9e%e6%95%b8%e8%88%87%e7%8d%8e%e5%8b%b5

    The third accordion panel under “Politiques” does not expand or collapse when you click on it.

    I looked into the WordPress core code and it does seem to be a valid issue, not a local environment issue. It was a good PHP and WordPress learning experience for me.

    I logged a ticket in WordPress core with my findings here: Policies accordion does not work for plugin names with non-English characters. This is my first WordPress core trac ticket, and I am happy that I get to contribute something like this.

    Another user confirmed the issue and came up with some fixes. We have some healthy discussions. I mentioned that I would like to contribute a proper fix for the issue when I have the opportunity to do that.

    If you are interested to know more about the technical details and follow the development progress, you can visit the ticket above and click on the “watch” button in the page.

  • “gpg: signing failed: Inappropriate ioctl for device”

    “gpg: signing failed: Inappropriate ioctl for device”

    I was doing a release for a WooCommerce extension using our internal release tool. In the middle of the release process, I noticed there is an error that says “gpg: signing failed: Inappropriate ioctl for device” in the terminal. (Unfortunately I have closed the terminal and I don’t have the full error message anymore.)

    It seems that suddenly my machine can’t make a verified git commit using my gpg signing key. I don’t know why. Maybe it’s due to the recent Mac OS update. Maybe it’s due to me restarting my machine. Maybe because I recently ran brew update and / or brew upgrade. 🤷‍♂️

    Below is how I fixed the problem. I’m documenting this in case it help someone else, and for my future self.

    The solution

    I googled the error message and I found this GitHub issue: gpg: signing failed: Inappropriate ioctl for device.

    It points to this Stack Exchange answer, which suggests to set  GPG_TTY environment variable.

    I run printenv in the terminal and I noticed that I do not have that environment variable.

    Since I use Oh My Zsh, I added the following into my ~/.zshrc file:

    # For GPG to sign git commits. 
    # See https://unix.stackexchange.com/a/257065/261332.
    GPG_TTY=$(tty)
    export GPG_TTY
    

    GPG_TTY then shows up in the printenv command output.

    However, that still does not work. I still could not create a new git commit for any git repo.

    Since I suspect it may be due to recent Mac OS update, I googled “unable to sign git commit after mac update”, and I found this Stack Overflow answer which mentions pinentry. I saw the term pinentry in the full error message in the terminal earlier.

    So I followed the instruction in the answer and did the following in the terminal:

    1. brew install pinentry-mac
    2. gpg --version – I already have version 2, so I’m good here.
    3. echo "pinentry-program $(which pinentry-mac)" >> ~/.gnupg/gpg-agent.conf
      • This adds pinentry-program /opt/homebrew/bin/pinentry-mac into the gpg-agent.conf file.
    4. gpgconf --kill gpg-agent

    Then, I tried to create a new git commit. A credential prompt window asked me to key in my password. It works! 🎉

  • Making @wordpress/scripts hot reload work for DDEV sites

    In @wordpress/scripts npm package documentation, there is an npm run start:hot command that starts the build for development with “Fast Refresh”. Under the hood, it uses webpack devServer.

    When I tried it out in my local DDEV sites, it does not work as expected.

    I also remember that I tried out Set up “React Fast Refresh” in woocommerce-admin #37165, and it did not work well / not working as expected for me.

    I took the opportunity to look into it during my WordPress and PHP development and learning last week.

    The Issue

    When we run the npm run start:hot command and make some code changes, the webpage in tn the browser does not reload / refresh / update automatically. We have to manually refresh the page.

    If we look into the browser console, we would see a bunch of console error stacking up:

    [webpack-dev-server] Invalid Host/Origin header
    [webpack-dev-server] Disconnected!
    [webpack-dev-server] Trying to reconnect...
    

    And if we look into the network panel, we would see a bunch of websocket requests like this – notice the request URL and origin in request headers:

    Doing a quick Google search on the “Invalid Host/Origin header” error message will bring us to the following GitHub issue: “Invalid Host/Origin Header” warning #1604.

    In the issue, one of the comments mentioned using allowedHosts: "all".

    If we look into the webpack.config.js in @wordpress/scripts package, it uses allowedHosts: 'auto'. See the webpack documentation for its meaning.

    Because of the 'auto' value, the hot reload does not work with our DDEV site with custom domain name.

    The Solution

    The easiest solution is to have a custom webpack.config.js that overrides the 'auto' value with our own value of 'all':

    const defaultConfig = require("@wordpress/scripts/config/webpack.config");
    module.exports = {
      ...defaultConfig,
      devServer: {
        ...defaultConfig.devServer,
        allowedHosts: "all",
      },
    };
    
    

    Note that as per webpack documentation, using 'all' is not recommended as apps that do not check the host are vulnerable to DNS rebinding attacks.

    Alternatively, we can allow all DDEV sites by using the following:

    allowedHosts: ".ddev.site"
    

    With this in place, hot reload would work as expected for DDEV sites.

  • WordPress Meetup Kuala Lumpur (Feb 2024) – Recap

    This is a recap post for WordPress Meetup Kuala Lumpur (Feb 2024) – WordPress for Developers. I was a speaker and I gave a talk about “Developing Plugin with React and WordPress NPM Packages”.

    The Details:

    • Name: WordPress Meetup Kuala Lumpur (Feb 2024) – WordPress for Developers
    • Location: Agmo Space, Multimedia University – MMU Cyberjaya, Persiaran Multimedia, 63000 Cyberjaya, Selangor, Malaysia (Google Maps)
    • Date(s): February 24, 2024 (Saturday)
    • Website: https://www.meetup.com/kuala-lumpur-wordpress-meetup/events/298750694/
    • Brand(s) sponsoring: None.
    • Event size:
      • Expected number of attendees: 68 registered attendees (from meetup.com page)
      • Actual number of attendees: About 40 people, based on my rough counting.
    • Participant breakdown/target:
      • Organizer’s description: None.
      • Table visitors: None.
      • Other observations: None.
    • Was the event inclusive: Yes.

    Things that went well: 

    • My presentation went well. It took about 45 minutes. There were some questions and engagements about the way we build WooCommerce admin pages with API in PHP and front-end in React, JavaScript and TypeScript.
      • I asked the audience about their experience with front-end libraries. One attendee uses React, and another one attendee uses Vue.js.
      • I showed some code generated with AI (VS Code with GitHub copilot). This sparked some questions and discussions in networking session.
      • I showed them how we build React pages in WooCommerce core plugin, and how we build extensions like Google Listings and Ads and Pinterest for WooCommerce that integrate with WooCommerce, with Integrating admin pages into WooCommerce extensions as reference.
      • I encourage them that they can build WooCommerce extensions, or even build their own plugin and extensions business by referring to our open source codes.
    • Louis’ presentation on building a WordPress plugin business was good. It took about 1 hour 30 minutes, covering a lot of topics like researching ideas, delivering MVP, marketing, pricing etc. It generated a lot of questions and interests.
      • Louis is positive about WooCommerce. He mentioned WooCommerce numerous times. He thinks it is good and he encourages attendees to use it too.
      • At one point, an attendee asked about WooCommerce marketplace and fees to get extensions listed in the marketplace. Louis directed the questions to me. I do not know the exact answer, but I did my best to answer it, highlighting the benefits of WooCommerce marketplace.
      • Louis builds his own licensing server. He showed us some of his code in his private GitHub repo.
    • Networking session – notable people:
      • I met Arthur Wong (from WordPress Meetup Kuala Lumpur (Dec 2023)) and wanted to follow up with him on his WordPress.com experience, his Taichi booking project, and his industrial property project. Unfortunately he had to leave early and we didn’t talk much.
      • Daniel Lang (AI engineer / founder) came and had a chat with me, since I am from Automattic and I mentioned AI briefly throughout the event. He told me that he knows @etobiesen for many years. We had a great chat talking about many things, mostly about AI, and it even extended into our LinkedIn messages. In my last messages with him, I told him about Jetpack AI assistant. He said he didn’t know about it and he will take a look.
      • I spoke to Christine Tee (Staff Machine Learning Engineer at Turing.com), because she is the one who uses Vue.js when I asked during my presentation earlier. I was interested and asked her questions about AI and her experience in Turing.com, partly because previously I had a job via Turing.com. She is active in Google Developers Groups and she gives talks in public speaking events. We chat until the end of the event. I asked if she is interested in joining Automattic, and she said not at this moment.

    Things that we could improve next time:  

    • A location that is closer to Kuala Lumpur city. I know of a few people who couldn’t make it because Cyberjaya is too far away for them. I asked Liew (the organizer) about this, and he said, given the time, that is the best sponsored place that he could find.

    Commonly asked questions: 

    • An attendee asked if there is a way to automatically sync data changes from the database to the wp-data in the browser.
    • An attendee asked if they can build similar front-end but using other library like Angular or Vue, not React.

    Photos: 

  • WordPress Meetup Kuala Lumpur – 24th February 2024

    I’ll be speaking in the WordPress Meetup Kuala Lumpur (Feb 2024) – WordPress for Developers event this coming Saturday 24th February, 2024.

    • Date: February 24, 2024 (Saturday)
    • Time: 2:00PM – 5:00PM (UTC+8)
    • Venue: Agmo Space, Multimedia University – MMU Cyberjaya, Persiaran Multimedia, 63000 Cyberjaya, Selangor, Malaysia (Google Maps)
    • Speakers:
      • Louis Gan from Appfromlab
      • Gan Eng Chin from WooCommerce / Automattic Inc.

    I’ll talk about how to leverage React and WordPress NPM packages to build wp-admin pages in your WordPress plugins to provide the best user experience.

    Join us!

  • Mission

    Responding to the Bloganuary January 9 prompt:

    Daily writing prompt
    What is your mission?

    This is deep.

    I feel like I am still learning and finding mine in my life.

    Or perhaps I shouldn’t look at my life-long mission now, since I haven’t really figured out one.

    A short-term mission.

    To get more people (especially in Malaysia) to know more about Automattic and its remote distributed work culture.

    Because I find many people and companies have doubts and do not subscribe to this idea. I think it is a shame or a waste, and I would like to do my part to change it, even if it’s just a little.

    That’s one of the reasons why I enjoy attending WordPress meetups and WordCamps. And doing presentations and talks about our P2.

    That’s one of the reasons why I setup this blog.

    Perhaps this short-term mission might eventually end up being my life-long mission.

  • Living a very long life

    Responding to the Bloganuary January 8 prompt:

    Daily writing prompt
    What are your thoughts on the concept of living a very long life?

    It depends.

    If everyone can live for a very long life (say 100 years old at the minimum), but every other factors remain the same, then I feel like the quality of life may become bad or terrible.

    There would be more and more people, on a crowded and limited space we call Earth. Resources will become lesser.

    Unless we are not confined to just Earth. If we are able to travel to and live in other planets, then that may not sound like a bad idea after all.

    But in the end, what is the purpose of life?

    If we do not have a purpose in our lives, then what is the meaning of living a very long life? What difference does it make?

    Would it be boring? Would it even be a torture?

    Unless we never stop learning. To make long lives purposeful and meaningful.

    That’s the meaning of life.