#!/usr/bin/env bash installpath="/usr/local" if [[ `whoami` != "root" ]]; then echo run as root exit 1 fi if [[ `which jq` == "" ]]; then apt update && apt install -y jq fi if [[ `which curl` == "" ]]; then apt update && apt install -y curl fi check=`curl https://golang.org/dl/?mode=json` stable=`echo $check | jq -r '.[0].stable'` if [[ "$stable" != "true" ]]; then exit 0 fi newversion=`echo $check | jq -r '.[0].version'` currentversion=`$installpath/go/bin/go version 2> /dev/null` if [[ "$currentversion" == *"$newversion"* ]]; then exit 0 fi cd /tmp file=$newversion.linux-amd64.tar.gz `curl https://dl.google.com/go/$file > $file` `rm -rf $installpath/go/` `tar -C $installpath -xzf $file` `rm $file`
Author: Andrei
Trim string starting suffix
Today I wanted to remove, from a string, the substring starting with a specified string.
For a line of code with a comment started with #, I wanted to remove everything starting at #, so the line “line with #comm#ent” should become “line with “.
The test for this is:
func TestRemoveFromStringAfter(t *testing.T) { tests := []struct { input, after, expected string }{ { input: "line with #comm#ent", after: "#", expected: "line with ", }, { input: "line to clean", after: "abc", expected: "line to clean", }, { input: "line to clean", after: "l", expected: "", }, { input: "", after: "", expected: "", }, { input: " ", after: "", expected: " ", }, } for i, test := range tests { result := RemoveFromStringAfter(test.input, test.after) if result != test.expected { t.Fatalf("Failed at test: %d", i) } } }
I tried to use TrimSuffix and TrimFunc from the strings package, but they weren’t getting me where I wanted. Then, all of a sudden, it stroke me: a string can be treated as a slice and a subslice is what I need. A subslice which ends right before the position of the suffix I give.
So I take the position of the suffix and extract a substring of the input string:
func RemoveFromStringAfter(input, after string) string { if after == "" { return input } if index := strings.Index(input, after); index > -1 { input = input[:index] } return input }
Test multiple PHP exceptions
There are cases when you have a base exception class which is extended by other exceptions. For a package called User, you could have a UserException which is inherited by UserNotFoundException, UserPasswordException and so on.
The base exception of your package allows you to catch any thrown exception from inside the package.
So there’s a base exception, a specific one and the service using them:
class BaseException extends Exception { } class NoItemsException extends BaseException { } class Service { /** * @param array $items * @throws NoItemsException */ public function save(array $items) { if (count($items) === 0) { throw new NoItemsException('Nothing to save'); } } }
And you want to catch all package exceptions:
$service = new Service(); try { $service->save([]); } catch (BaseException $e) { die($e->getMessage()); }
And a test for the case:
class ServiceTest extends TestCase { /** * @expectedException NoItemsException */ public function testSaveWithNoItems() { $service = new Service(); $service->save([]); } }
How do you make sure NoItemsException extends BaseException?
With the above test, if you change NoItemsException to extend Exception instead of BaseException, the test will still pass, but the behaviour won’t be the expected one, because you won’t be catching the exception anymore:
Fatal error: Uncaught NoItemsException: Nothing to save... in /src/Service.php on line 20 NoItemsException: Nothing to save... in /src/Service.php on line 20
Your test must explicitly test both exceptions:
class ServiceTest extends TestCase { public function testSaveWithNoItems() { $service = new Service(); try { $service->save([]); $this->fail('No exception thrown'); } catch (\Exception $e) { $this->assertInstanceOf(BaseException::class, $e); $this->assertInstanceOf(NoItemsException::class, $e); } } }
QA tools for Go apps in CI/CD
Go Meta Linter is a great tool to run code quality checks including vet, static analysis, security, linting and others. I’ve used it a few times, enjoyed it, and I’ve built a basic setup to include it into CI/CD, along with the unit tests execution.
All you need is Docker and a Docker images repository like Docker Hub. You’ll build an image to run the tools in, push it to your repository, then pull it on your CI/CD machine and run a container from it, as simply as:
docker run -ti --rm \ -e PKG=github.com/andreiavrammsd/dotenv-editor \ -e CONFIG=dev/.gometalinter.json \ -v $PWD:/app \ yourdockerusername/go-qa-tools \ make
(later edit: I archived the example package)
Of course, it can be integrated into a service like Travis:
sudo: required language: minimal install: - docker pull andreiavrammsd/go-qa-tools script: - docker run -ti -e PKG=github.com/andreiavrammsd/dotenv-editor -e CONFIG=dev/.gometalinter.json -v $PWD:/app andreiavrammsd/go-qa-tools make
See the full Go QA tools setup on GitHub.
Database migrations
Database migrations can be easily integrated into your deploy system, running as a decoupled process, so it can be replaced anytime by other tools if needed, and working with it without interfering with the project itself.
The entire process can be isolated into a Docker container or the tools can be all installed directly on your machine. Presented setup is for CentOS.
Let’s assume the following context:
– Machine to run the migrations from (with Docker installed)
– MySQL database with access from the machine mentioned above
– A secrets manager to keep the database access credentials safe
– Git repository holding the migration files included (there will be a directory with all the migration files in the proper format).
– Private SSH key to access the above mentioned repository
Every time you deploy your app, you could run all the migrations you committed to your repository. Your deploy system should trigger the migration tool at the proper moment.
The key in this setup is migrate, a flexible tool which I had no problems with.
As presented in this Dockerfile, there are different tools used to perform each required step:
– Get migration files from the repository
– Get a secret string with database credentials from the secrets manager
– Extract the database credentials from the secret string
– Execute the migrations
Take a look at the full setup on GitHub.
PHP unit testing with real coverage
If you really need to cover all your code by tests, watch out for your short if statements.
Given the following class:
<?php class Person { /** * @var string */ private $gender; /** * @param string $gender */ public function setGender(string $gender) { $this->gender = $gender; } /** * @return string */ public function getTitle() : string { return $this->gender === 'f' ? 'Mrs.' : 'Mr.'; } }
And a PHP Unit test:
<?php use PHPUnit\Framework\TestCase; class PersonTest extends TestCase { /** * @dataProvider gendersAndTitle * @param $gender * @param $expectedTitle */ public function testTitle($gender, $expectedTitle) { $person = new Person(); $person->setGender($gender); $title = $person->getTitle(); $this->assertEquals($expectedTitle, $title); } public function gendersAndTitle() : array { return [ ['f', 'Mrs.'], ]; } }
If you run the test with coverage, you get a 100% coverage. But the data provider has only data for the “f/Mrs.” case, so the else branch of the short if is not actually tested, though the tested code reached the line while running the test.
Update the getTitle method from Person class using the normal if statement:
public function getTitle() : string { if ($this->gender === 'f') { return 'Mrs.'; } return 'Mr.'; }
Execute the test again and you get 80% coverage.
Here’s a Dockerfile to quickly test it yourself:
FROM php:7.2-cli-alpine3.8 RUN apk add --update --no-cache make alpine-sdk autoconf && \ pecl install xdebug && \ docker-php-ext-enable xdebug && \ apk del alpine-sdk autoconf && \ wget -O phpunit https://phar.phpunit.de/phpunit-6.phar && chmod +x phpunit WORKDIR /src
Save the Person class to Person.php file and the test to PersonTest.php.
docker build -t phpunit-coverage . docker run --rm -ti -v $PWD:/src phpunit-coverage sh ./phpunit --bootstrap Person.php --coverage-html coverage --whitelist Person.php .
See the coverage directory (index.html) created after running the test.
Clean up when you’re done:
docker rmi phpunit-coverage
Match sorted and unsorted integers
I was wondering if there’s a performance difference between matching the integers from two slices, once if the numbers are sorted and once if they’re not. I didn’t stress the hell out of the situation, I went up to 10k numbers.
For small sets, of course, the difference is not worth mentioning. For large slices, if you really, really focus on performance, you could be better with sorted values, if the values are already sorted; if you sort them each time, the loss will be there.
var a = []int{ ... } var b = []int{ ... } func IterateNotSorted() int { count := 0 for _, i := range a { for _, j := range b { if i == j { count++ break } } } return count } var c = []int{ ... } var d = []int{ ... } func IterateSorted() int { count := 0 for _, i := range c { for _, j := range d { if i == j { count++ break } } } return count }
Fill in the slices with some numbers and test it yourself.
func BenchmarkIterateNotSorted(b *testing.B) { for n := 0; n < b.N; n++ { IterateNotSorted() } } func BenchmarkIterateSorted(b *testing.B) { for n := 0; n < b.N; n++ { IterateSorted() } }
A few things I’ve enjoyed reading lately
Docker multi-stage builds with Docker Compose
When defining a multi service environment with Docker and Docker Compose, the usual way was to use a Dockerfile for each service, starting with the base image and adding all custom needs:
/env/php/Dockerfile
FROM php:7.2-fpm-alpine3.7 RUN docker-php-ext-install opcache
/env/nginx/Dockerfile
FROM nginx:1.15-alpine ADD virtual-host.conf /etc/nginx/conf.d/default.conf
Then you could compose all services.
/docker-compose.yml
version: '3' services: php: build: context: ./env/php volumes: - ./:/app working_dir: /app restart: unless-stopped nginx: build: context: ./env/nginx volumes: - ./:/app ports: - "80:80" restart: unless-stopped
Then Docker 17.05 introduced multi-stage builds, allowing to use one Dockerfile. Continue reading Docker multi-stage builds with Docker Compose
PHP performance increase from 5.6 to 7.2
I remember when PHP 7 was released. The first thing I did was a simple performance test. I don’t remember the script exactly, but it was similar to this:
<?php $time = microtime(true); $array = []; for ($i = 0; $i < 10000; $i++) { if (!array_key_exists($i, $array)) { $array[$i] = []; } for ($j = 0; $j < 1000; $j++) { if (!array_key_exists($j, $array[$i])) { $array[$i][$j] = true; } } } echo sprintf( "Execution time: %f seconds\nMemory usage: %f MB\n\n", microtime(true) - $time, memory_get_usage(true) / 1024 / 1024 );
The results made me really happy. Continue reading PHP performance increase from 5.6 to 7.2