assertRaises in table-driven tests

This is what I want to do but it fails passing None to assertRaises:

tests = [
   (0, None),
   (1, None),
   (-1, TooFewException),
   (99, None),
   (100, TooManyException),
]
for n, exc in tests:
    with self.assertRaises(exc):
        results = my_code(n)
        assert len(results) == n

Here’s a version of assertRaises that will let you do that:

    def assert_raises(self, exception: Type[Exception]):
        if exception:
            return self.assertRaises(exception)
        return contextlib.nullcontext()

Line Count

Number of lines in a (python + js) codebase:

#!/bin/zsh

# This script counts the number of non-blank lines of code in a directory and its subdirectories
# for Python and JavaScript code only

# Set the directory to search for code files
dir='.'

# Count the number of non-blank lines of code for Python and JavaScript files
num_lines=$(find "$dir" -type f \( -name '*.py' -or -name '*.js' \) -not -path '*venv*' -not -path '*node_modules*' -exec grep -he '^[^[:space:]]' {} + | awk 'NF{count++} END{print count}')

echo "Number of non-blank lines of code: $num_lines"

Number of lines per-file in a (python + js) codebase:

#!/bin/zsh

# This script counts the number of non-blank lines of code in a directory and its subdirectories
# for Python and JavaScript code only

# Set the directory to search for code files
dir='.'

# Count the number of non-blank lines of code for Python and JavaScript files
num_lines=$(find "$dir" -type f \( -name '*.py' -or -name '*.js' \) -not -path '*venv*' -print0 | xargs -0 grep -cve '^[[:space:]]*
)

echo "Number of non-blank lines of code: $num_lines"

Queries on local so much faster than queries on server

I was encountering a situation where big aggregating queries that took seconds on local were taking minutes or even hours on prod. After trying about a million things this Stack Exchange post finally cleared things up.

Setting innodb_buffer_pool_size = 5G in MariaDB’s configuration and restarting instantly changed the queries to taking seconds. The default is 128M which is … not useful 🙂

Change Terminal Background Color When ssh’ed – Mac

Save this as “ssh” somewhere on your $PATH ahead of /usr/bin/ssh and make it executable

#!/bin/sh

HOSTNAME=$(echo $@ | sed 's/.*@//')

set_theme () {
    osascript -e "tell application \"Terminal\" to set current settings of first window to settings set \"$1\""
}

on_exit () {
    set_theme "Solarized Light"
}

trap on_exit EXIT

case $HOSTNAME in
  # These all do the same thing but we could have different themes for different locations
  hb|pd|decksite) set_theme "Solarized Dark" ;;
  *) set_theme "Solarized Dark" ;;
esac

/usr/bin/ssh "$@"

Set Up a New Mac (August 2023 Edition)

Previously:
July 2019,
July 2022

  • Upgrade to latest OSX.
  • Restore u directory from Backblaze backup. Restore dotfiles, .ssh dir, .vim dir, maybe other dot directories from Backblaze backup. Restore ~/Library/Application Support/Alfred 4/*.
  • Alfred (and give it Accessibility access, add license key).
  • Set new hostname with:
    # Respectively, fully-qualified hostname, Bonjour hostname, the user-friendly computer name you see in Finder, flush the DNS cache …
    $ sudo scutil --set HostName agrajag.bluebones.net
    $ sudo scutil --set LocalHostName agrajag
    $ sudo scutil --set ComputerName agrajag
    $ dscacheutil -flushcache
    
  • Dock: remove everything, hiding on, magnification on.
  • Sublime Text 4 (add license key, set "trim_trailing_white_space_on_save": true in sublime prefs, install Package Control, install Solarized Theme).
  • subl commandline sublime.
    $ ln -s "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" ~/u/bin/subl
  • sublime-git-ignorer: (1) add Package Control repo https://github.com/apc999/sublime-text-gitignore (2)
    add package sublime-text-gitignore (3) use menu item : File->Exclude Git-ignored (4) Give this a keyboard shortcut in Sublime, Preferences, Key Bindings with { "keys": ["super+shift+i"], "command": "exclude_ignored" }
  • Keyboard shortcut to toggle word wrap in Sublime Text. Preferences, Key Bindings { "keys": ["super+shift+w"], "command": "toggle_setting", "args": { "setting": "word_wrap" } }
  • Install FiraCode and add it to Sublime Text prefs with "font_face": "Fira Code".
  • Hide Desktop icons
    $ defaults write com.apple.finder CreateDesktop false
    $ killall Finder
  • Solarized theme for Terminal and make default in preferences.
  • “Use Option as Meta Key” in Terminal to make left Alt work as Esc.
  • Terminal, Settings, Advanced, un-check “Audible bell” and “Only when sound is muted”
  • System Preferences, Sound, turn “Alert volume” all the way down
  • Use Ctrl-f7 to enable keyboard navigation of dialogs
  • Homebrew. (Installs OSX commandline tools.)
  • brew install mariadb
  • brew install npm
  • brew install apache2. Docker Desktop (below) uses port 8080 so edit /opt/homebrew/etc/httpd/httpd.conf to use port 8081 and remember that you did it.
  • brew install php. Enable in Apache with LoadModule, FilesMatch and DirectoryIndex directives as per homebrew output and brew services restart apache2.
  • brew install gh
  • brew install composer
  • Docker Desktop.
  • Discord.
  • Change all .txt to open with Sublime Text.
  • Show hidden files in Finder. Cmd-Shift-.
  • WhatsApp.
  • 1Password.
  • Chrome. (Log in to Chrome to get uBlock Origin and other extensions.)
  • Skitch.
  • Backblaze.
  • Parallels Desktop + MTGO
  • npm install -g rtm-cli
  • Disable Ask Siri in System Preferences, Siri
  • Turn on “tap to click” in System Preferences, Trackpad.
  • Disable long-press (it’s wayyyyy too annoying when selecting text in Chrome): defaults write -g ApplePressAndHoldEnabled -bool false
  • Zoom.
  • Skype.
  • IntelliJ IDEs for languages that you’re currently using from the list below. Install Solarized theme and switch to Fira Code as editor font for each.
    • PyCharm.
    • GoLand.
    • WebStorm.
    • PHPStorm.
  • Spotify.
  • Slack.
  • git clone important repos (ff, pd, server)
  • Restore any local databases for dev (ff, decksite, logsite)
  • ln -s ~/ff/src/www /opt/homebrew/var/www/ff
  • GRANT ALL ON ff.* TO ff@localhost IDENTIFIED BY *REDACTED*
  • pd setup

The 138 Terminal Positions of Tic-Tac-Toe

Also known as Noughts and Crosses.

A Rust program to generate all terminal positions of the game of Tic-Tac-Toe. Rotations, transpositions, games-still-in-progress and impossible board states are not shown.

github.com/bakert/boards

use std::collections::HashMap;

const EMPTY: char = ' ';
const X: char = 'X';
const O: char = 'O';

type Boards = HashMap;
type Board = [[char; 3]; 3];
type Representations = [String; 8];

trait BoardMethods {
    fn display(&self) -> String;
    fn is_terminal(&self) -> bool;
    fn representation(&self) -> String;
    fn all_representations(&self) -> Representations;
    fn rotate(&self) -> Board;
    fn transpose(&self) -> Board;
}

trait BoardsMethods {
    fn contains(&self, b: Board) -> bool;
    fn insert_board(&mut self, b: Board);
}

fn main() {
    test();
    let empty_board: Board = [[EMPTY; 3]; 3];
    let mut final_boards: Boards = HashMap::new();
    let boards: Vec = all_boards(empty_board, X);
    for board in boards {
        if board.is_terminal() && !final_boards.contains(board) {
            final_boards.insert_board(board);
        }
    }
    println!("{} unique terminal boards found\n", final_boards.len());
    for (_, board) in final_boards {
        println!("{}", board.display());
    }
}

fn all_boards(board: Board, to_play: char) -> Vec {
    let to_play_next: char = if to_play == X { O } else { X };
    let mut boards: Vec = Vec::new();
    for x in 0..board.len() {
        let row = board[x];
        for y in 0..row.len() {
            if board[x][y] == EMPTY {
                let mut b: Board = board.clone();
                b[x][y] = to_play;
                boards.push(b);
                if !b.is_terminal() {
                    boards.append(&mut all_boards(b, to_play_next));
                }
            }
        }
    }
    return boards;
}

impl BoardMethods for Board {
    fn display(&self) -> String {
        let mut s = String::new();
        for row in self {
            for square in row {
                s.push('|');
                s.push(*square);
            }
            s.push_str("|\n");
        }
        return s;
    }

    fn is_terminal(&self) -> bool {
        for i in 0..3 {
            if self[i][0] != EMPTY && self[i][0] == self[i][1] && self[i][1] == self[i][2] {
                return true;
            }
            if self[0][i] != EMPTY && self[0][i] == self[1][i] && self[1][i] == self[2][i] {
                return true;
            }
        }
        if self[0][0] != EMPTY && self[0][0] == self[1][1] && self[1][1] == self[2][2] {
            return true;
        }
        if self[0][2] != EMPTY && self[0][2] == self[1][1] && self[1][1] == self[2][0] {
            return true;
        }
        for row in self {
            for square in row {
                if *square == EMPTY {
                    return false;
                }
            }
        }
        return true;
    }

    fn all_representations(&self) -> Representations {
        let mut r: Representations = Default::default();
        r[0] = self.representation();
        r[1] = self.rotate().representation();
        r[2] = self.rotate().rotate().representation();
        r[3] = self.rotate().rotate().rotate().representation();
        r[4] = self.transpose().representation();
        r[5] = self.rotate().transpose().representation();
        r[6] = self.rotate().rotate().transpose().representation();
        r[7] = self.rotate().rotate().rotate().transpose().representation();
        return r;
    }

    fn representation(&self) -> String {
        let mut s = String::new();
        for row in self {
            for square in row {
                s.push(*square);
            }
        }
        return s;
    }

    fn rotate(&self) -> Board {
        let mut b: Board = [[EMPTY, EMPTY, EMPTY]; 3];
        for x in 0..self.len() {
            let row = self[x];
            for y in 0..row.len() {
                b[x][y] = self[row.len() - y - 1][x];
            }
        }
        return b
    }

    fn transpose(&self) -> Board {
        let mut b: Board = [[EMPTY, EMPTY, EMPTY]; 3];
        for x in 0..self.len() {
            let row = self[x];
            for y in 0..row.len() {
                b[y][x] = self[x][y];
            }
        }
        return b;
    }
}

impl BoardsMethods for Boards {
    fn contains(&self, board: Board) -> bool {
        for repr in board.all_representations() {
            if self.contains_key(&repr) {
                return true;
            }
        }
        return false;
    }

    fn insert_board(&mut self, b: Board) {
        self.insert(b.representation(), b);
    }
}

fn test() {
    test_rotate();
    test_transpose();
}

fn test_rotate() {
    // XXX    _OX
    // OOO => _OX
    // ___    _OX
    let b: Board = [[X, X, X], [O, O, O], [EMPTY, EMPTY, EMPTY]];
    let rotated: Board = b.rotate();
    assert!(rotated[0] == [EMPTY, O, X]);
    assert!(rotated[1] == [EMPTY, O, X]);
    assert!(rotated[2] == [EMPTY, O, X]);
}

fn test_transpose() {
    // X_O    XO_
    // OX_ => _X_
    // __X    O_X
    let b: Board = [[X, EMPTY, O], [O, X, EMPTY], [EMPTY, EMPTY, X]];
    let transposed: Board = b.transpose();
    assert!(transposed == [[X, O, EMPTY], [EMPTY, X, EMPTY], [O, EMPTY, X]]);
}