Laravel 7.x : Travis CIでUnitテストを実行する

Travis CI: Travis側(Ubuntu)でテストが行われる
Jenkins: テスト環境の構築が必要

### TravisCIの機能
– 自動でソースコードをcheckoutして、予め指定しておいたビルドやテスト処理を実行
– テスト結果をTravis CIのサイト上や各種API、メールで開発者に通知
– テストが問題なければ、予め指定しておいたホスティングサービスにデプロイする
– ビルドやテスト処理は「.travis.yml」に記述する

### 前準備(ローカル環境)
– Vagrant Amazon Linux 2
– Laravel 7.3.0, MySQL 8.0.18

### TravisCI側の操作
– Approve & Install Travis CI
— Only select repositories: publicを選択
— approve & install

### .travis.yml作成
– Laravelのルートディレクトリに.travis.ymlを作成

language: php
php:
  - 7.3

before_script:
  - composer self-update
  - composer install
  - chmod -R 777 storage
  - php artisan key:generate

script: 
  - ./vendor/bin/phpunit

notifications:
  emails:
    - hoge@gmail.com
1) Tests\Feature\ExampleTest::testBasicTest
RuntimeException: No application encryption key has been specified.

APP_KEYが必要のようなので、before_scriptでAPP_KEYを読み込むようにします。
.env.travis

APP_ENV=testing
APP_KEY=base64:*****
before_script:
  - cp .env.travis .env
  - composer self-update
  - composer install
  - chmod -R 777 storage

0.23s$ ./vendor/bin/phpunit
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 165 ms, Memory: 20.00 MB
OK (2 tests, 2 assertions)
The command “./vendor/bin/phpunit” exited with 0.

割とすぐ上手くいきました。仕組みとしては、CircleCIと似ていますが、Dockerじゃないとこが異なりますね。

CircleCIからEC2にデプロイする際にThe authenticity of host ” can’t be establishedが出た時

$ ssh-keygen -m pem
$ cd .ssh
$ ls
authorized_keys config id_rsa id_rsa.pub

id_rsa(秘密鍵)を Circle CI -> PROJECT SETTINGS -> Add SSH Keyに登録
その際、Host NameはインスタンスのIPアドレスを登録する

id_rsa.pub(公開鍵)をEC2 .ssh/authorized_keysに追加

$ git push -u origin master
The authenticity of host ‘************* (*************)’ can’t be established.

config.yml

      - run: ssh -o 'StrictHostKeyChecking no' ${USER_NAME}@${PUBLIC_IP} 'cd /var/www && git pull'
#!/bin/bash -eo pipefail
ssh -o 'StrictHostKeyChecking no' ${USER_NAME}@${PUBLIC_IP} 'cd /var/www && git pull'
Warning: Permanently added '*************' (ECDSA) to the list of known hosts.

fatal: not a git repository (or any of the parent directories): .git

Exited with code exit status 128

あ、エラー内容が変わりました。

$ cd /var/www
$ sudo git clone https://github.com/hoge/circleci.git

config.yml

      - run: ssh -o 'StrictHostKeyChecking no' ${USER_NAME}@${PUBLIC_IP} 'cd /var/www/circleci && git pull'

error: cannot open .git/FETCH_HEAD: Permission denied
$ sudo chmod 777 .git/
$ git remote add origin https://{username}:{password}@github.com/{username}/project.git

error: insufficient permission for adding an object to repository database .git/objects
fatal: failed to write object
fatal: unpack-objects failed

$ sudo chmod 777 objects/

error: cannot lock ref ‘refs/remotes/origin/master’: Unable to create ‘/var/www/circleci/.git/refs/remotes/origin/master.lock’: Permission denied

$ git remote prune origin

There is no tracking information for the current branch.
Please specify which branch you want to merge with.

error: unable to unlink old ‘.circleci/config.yml’: Permission denied
error: unable to unlink old ‘resources/views/home.blade.php’: Permission denied

$ sudo chown -R ec2-user ./

やっといけました。1日かかりました。
他の方は ssh -o ‘StrictHostKeyChecking no’やgitのpermission変更などせずに行けているようなのであまり参考にならないかもしれませんが。

Circle CIでEC2にデプロイ

### 環境
– vagrant Amazon Linux2
– Laravel7.2.1, Mysql8.0

### 前提
– .circleci/config.ymlを配置済み
– git pushすると、phpunitが走る
– これにデプロイのjobを追加したい

公式: https://circleci.com/docs/ja/2.0/deployment-integrations/

公式のソースコード:
-> runコマンドを見るとgit pushでデプロイしていますね。

deploy:
    machine:
        enabled: true
    working_directory: ~/circleci-demo-workflows
    environment:
      HEROKU_APP: "sleepy-refuge-55486"
    steps:
      - checkout
      - run:
          name: Master を Heroku にデプロイ
          command: |
            git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP.git master

workflows:
  version: 2
  build-and-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: sequential-branch-filter

ec2にsshログインしているので、これをconfig.ymlに書いてやってみたいと思います。

ssh ec2-user@{publicIP} -i  ~/.ssh/***.pem

### 1. テスト用のEC2インスタンス作成
– Amazon Linux 2 AMI (HVM), SSD Volume Type 64bit
– t2.nano
– public subnet
– Auto-assign Public IP: enable
– gp2 8GiB
– SecurityGroup: SSH 0.0.0.0/0
– keypair: create | select

->SSHログインテスト
ssh ec2-user@{publicIP} -i ~/.ssh/***.pem

### 2. EC2にGitインストール
// インストール手順は省略
$ git –version
git version 2.19.2

$ cd /var
$ sudo mkdir www

### 3. CircleCI Project setting
CircleCIのコンソールにログインし、対象リポジトリのproject settingのページに遷移
– Environment Variables
KEY: PUBLIC_IP, VALUE: **.***.***.**
KEY: USER_NAME, VALUE: ec2-user

– SSH Permissions
Hostname: 任意
Private Key:秘密鍵の中身

-----BEGIN RSA PRIVATE KEY-----
// 省略
-----END RSA PRIVATE KEY-----

### 4.config.ymlにdeploy追加

version: 2
jobs:
  build:
    // buildは省略
  deploy:
    machine:
      image: circleci/classic:edge
    steps:
      - checkout
      - add_ssh_keys:
      - run: ssh ${USER_NAME}@${PUBLIC_IP} 'cd /var/www && git pull

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
        requires:
          - build
        filters:
          branches:
            only: master

### 5.git push
$ git push -u origin master
$ echo -e “Host github.com\n\tStrictHostKeyChecking no\n” > ~/.ssh/config

#!/bin/bash -eo pipefail
ssh ${USER_NAME}@${PUBLIC_IP} ‘cd /var/www && git pull’
The authenticity of host ‘************* (*************)’ can’t be established.

buildはsuccessだがdeployが何度やってもダメだ。。。丸一日潰れた。

ん?

EC2 インスタンス上で秘密鍵 (pem ファイル) を作成して CircleCI に登録する必要があります。

秘密鍵はec2で作成するの???
あああああああああ

プログラミング言語 人気ランキングの比較

### TIOBE
ロジック : Index Definition
-> 検索エンジンに対して、クエリを発行した結果を元にランキングを付けている。

+"<language> programming"

1位 Java, 2位 C, 3位 Python, 4位 C++, 5位 C#, 6位 Visual Basic.NET, 7位 JavaScript, 8位 PHP, 9位 SQL, 10位 Go

### PYPL
1位 Python, 2位 Java, 3位 Javascript, 4位 C#, 5位 PHP, 6位 C/C++, 7位 R, 8位 Objective-C, 9位 Swift, 10位 Matlab
ロジック: The Index is created by analyzing how often language tutorials are searched on Google

### RedMonk: Programming Language Ranking
ロジック: GitHubのアクティブリポジトリ、StackOverflowでの質問、Redditでの投稿数、求人サイトでの募集の数
1位 JavaScript, 2位 Python, 3位 Java, 4位 PHP, 5位 C#, 6位 C++, 7位 Ruby, 8位 CSS, 9位 TypeScript, 9位 C

### GitHub Octoverse
Top OSS
1位 microsoft/vscode, 2位 MicrosoftDocs/azure-docs, 3位 flutter/flutter, 4位 firstcontributions/first-contributions, 5位 tensoflow/tensorflow, 6位 facebook/react-native, 7位 kubernetes/kubernetes, 8位 DefinitelyTyped/DefinitelyTyped, 9位 ansible/ansible, 10位 home-assistant/home-assistant

Top Language
1位 JavaScript, 2位 Python, 3位 Java, 4位 PHP, 5位 C#, 6位 C++, 7位 TypeScript, 8位 Shell, 9位 C, 9位 Ruby

Google indexとGitHubだとランキングに差があるが、概ねJava, Python, JavaScriptが上位はほぼ変わらずか。

18.04bionicにHHVMを入れて試してみる

$ sudo apt install hhvm
$ hhvm –version
HipHop VM 3.21.0 (rel)
Compiler: 3.21.0+dfsg-2ubuntu2
Repo schema: ebd0a4633a34187463466c1d3bd327c131251849

$ echo ‘‘ >/tmp/hello.php
$ hhvm /tmp/hello.php
Hello HHVM

echo "hello world!";

$ hhvm hello.php

Fatal error: /home/vagrant/hhvm/hello.php appears to be a Hack file, but you do not appear to be running the Hack typechecker. See the documentation at http://docs.hhvm.com/hack/typechecker/setup for information on getting it running. You can also set `-d hhvm.hack.lang.look_for_typechecker=0` to disable this check (not recommended).

サイトを見ます。
getting-started

$ touch .hhconfig
$ hhvm hello.php
Warning: Hack typechecking failed: server not ready. Unless you have run “hh_client” manually, you may be missing Hack type errors! Once the typechecker server has started up, the errors, if any, will then show up in this error log.
hello world!

とりあえずHHVMの環境つくって動かしてみました ってところより先の記事が少ない。PHP7.0以上が高速になったので、みんな様子見ってところか。

HHVM/Hack

PHPは、ZendEngineというインタプリタエンジンを使って動いている

インタプリタの挙動
1. テキストファイルを読み込む(IO処理)
2. ソースコードをパースしてopcodeに変換(CPU処理)
3. opcodeを順番に実行

Opcacheを用いることで一度変換したopcodeをメモリ上に載せておき、2回目以降の処理はメモリにキャッシュされたopcodeを取り出して実行する

opcodeは中間言語の為、cpuやosなど実行環境に応じて実行される。その為、JavaやCなどのコンパイル言語よりも実行速度が遅い
c++やgolangは最初のコンパイルで機械語まで変換する

### Hip Hop Virtual Machine
PHPやHack言語をx64CPU向けにJITコンパイルするvirtual machine。つまり、Zend Engineで実行する代わりにJITコンパイルして実行する
HHVMはFacebook開発
HHVMはPHP7からは非推奨だが、Hack言語のサポート

### Hack言語
PHPに静的型付け機能を追加し、HHVMに最適化
開始するタグが < ?php ではなく < ?hhで、HTMLに埋め込む形ができない Hackは非同期機能をサポート Wikipedia, baiduなどが採用 PHPフレームワークやライブラリが使えない? -> Codeigniter, cake, laravel, Yiiなど動作する

### Hack言語の機能
Generic(型をパラメータとして与え、型だけ違って処理の内容が同じものを作るときに使う)
Map(keyとvalueをset) / Vector(順番に値を保持) / Set(重複不可) / Pair(2つの値をセット)
Enum(値を列挙)
async, awaitで並列実行
hh_client(実行前に構文チェック)

amazon linux2(vagrant) + Laravel 7.xでDuskを使うまでの手順

Laravelでseleniumを使おうと思ったら、duskというパッケージがあるらしいので、duskをインストールして使っていきます。環境は、vagrant、amazon linux2, laravel 7.2.1です。

### 1.duskインストール
$ php composer.phar require –dev laravel/dusk
– laravel/dusk v5.10.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.

$ sudo yum install php-pecl-zip.x86_64
$ php composer.phar require –dev laravel/dusk

$ php artisan dusk:install
testsディレクトリにBrowserディレクトリが作られる

.env

APP_URL=http://192.168.33.10:8000

### 2.duskテスト
$ php artisan dusk:make LoginTest

/tests/Browser/LoginTest.php

public function test_can_login_successfull(){
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                ->type('email', 'foo@hoge.com')
                ->type('password', 'hogehoge')
                ->press('Login')
                ->assertSee('this is subscriber');
        });
    }
$ php artisan dusk
1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\WebDriverCurlException: Curl error thrown for http POST to /session with params: {"capabilities":{"firstMatch":[{"browserName":"chrome","goog:chromeOptions":{"binary":"","args":["--disable-gpu","--headless","--window-size=1920,1080"]}}]},"desiredCapabilities":{"browserName":"chrome","platform":"ANY","chromeOptions":{"binary":"","args":["--disable-gpu","--headless","--window-size=1920,1080"]}}}

Failed to connect to localhost port 9515: Connection refused

ん? ChromeDriverが入っていない?

### 3.ChromeDriver インストール
$ ./vendor/laravel/dusk/bin/chromedriver-linux –verbose
./vendor/laravel/dusk/bin/chromedriver-linux: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory
$ sudo yum install -y libX11
$ ./vendor/laravel/dusk/bin/chromedriver-linux –v
ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882})

$ php artisan dusk
1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownErrorException: unknown error: cannot find Chrome binary

なに、今度はChrome binary?

### 4.Chrome install
$ curl https://intoli.com/install-google-chrome.sh | bash
$ php artisan dusk

1) Tests\Browser\LoginTest::test_can_login_successfull
Facebook\WebDriver\Exception\NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"body textarea[name='email']"}
  (Session info: headless chrome=80.0.3987.149)

どういうこと? 

### 5.visitでパスを直接指定

public function test_can_login_successfull(){
        $this->browse(function (Browser $browser) {
            $browser->visit('http://192.168.33.10:8000/login')
                // ->dump();
                ->type('email', 'foo@hoge.com')
                ->type('password', 'hogehoge')
                ->press('Login')
                ->assertSee('this is subscriber');
        });

    }
$ php artisan dusk
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 2.18 seconds, Memory: 18.00 MB

OK (1 test, 1 assertion)

上手くいきました。半日かかりましたが、seleniumが何やってるかは理解できました。

Seleniumとは?

公式:selenium
=> Selenium automates browsers
=> 自動でブラウザを操作することで、Webサイトの動作テストを行うことができる(結合試験、総合試験などに活用)
=> 自動で画面キャプチャを保存できる
=> Seleniumには旧API(Selenium RC)と新しいSelenium Web Driverがある
=> Selenium WebDriverはブラウザの拡張機能やOSのネイティブ機能を利用
=> Apache License 2.0, 「Windows」「OS X」「Linux」「Solaris」などをサポート

JSON Wire ProtocolというRESTful API*に対応しており、このAPIにしたがってリクエストすることでブラウザを操作することができる

JSON Wire Protocol ↔︎ Firefox Driver | Chrome Driver | IE Driver ↔︎ ブラウザ拡張機能/OSのネイティブ機能 ↔︎ ブラウザ

### 注意点
– テストコードが冗長になる
– 非同期処理は苦手

LaravelでSeleniumを使うには?
-> Duskというテストフレームワークが含まれている
-> DuskはPHPUnitとphp-webdriver、seleniumを使うためのパッケージ

Laravel7.x、EC2でphp artisan down –allow=${ip}が効かない時

本番環境でメンテナンスモードにして、特定のIPだけ操作を許可したい場合は、

$ php artisan down --allow=${ip}

と書く。EC2で実行したところ、指定したipでもメンテナンスモードの画面に。

EC2 ELB: ELBを使って、SSL対応している

ELBを挟んでいるため、ip取得のclassにHTTP_X_FORWARDED_FORを追加する
$ sudo vi vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php

public function handle($request, Closure $next)
    {
        if ($this->app->isDownForMaintenance()) {
            $data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);

            // 追加
            if (isset($data['allowed']) && IpUtils::checkIp($request->server->get('HTTP_X_FORWARDED_FOR'), (array) $data['allowed'])) {
                return $next($request);
            }

            if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {
                return $next($request);
            }

            if ($this->inExceptArray($request)) {
                return $next($request);
            }

            throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']);
        }

        return $next($request);
    }

※HTTP_X_FORWARDED_FOR: リバースプロキシを挟んだアクセス元のIPを取得する

これで、メンテナンスモード時に、特定IPのみ操作できるようになります。
$ php artisan down –allow=${myIP}
$ php artisan up

EC2のmysql8.0.19でmmap(137363456 bytes) failed; errno 12

EC2でmysqlが頻繁に落ちる。
暫定的に以下を実行すると再起動できるが、1日経つとまた落ちる。

sudo touch /var/lib/mysql/mysql.sock
sudo chmod 777 /var/lib/mysql/mysql.sock
sudo service mysqld restart

cronの実行が影響しているのか? cronを止めて、PDOを手動実行するも問題なし。

ログを確認するとmmap(137363456 bytes) failed;となっている

$ sudo cat /var/log/mysqld.log
2020-03-21T20:45:21.161522Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.19) starting as process 12458
2020-03-21T20:45:21.299847Z 0 [ERROR] [MY-012681] [InnoDB] mmap(137363456 bytes) failed; errno 12
2020-03-21T20:45:21.299951Z 1 [ERROR] [MY-012956] [InnoDB] Cannot allocate memory for the buffer pool
2020-03-21T20:45:21.300002Z 1 [ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error Generic error.
2020-03-21T20:45:21.301125Z 1 [ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine
2020-03-21T20:45:21.301283Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
2020-03-21T20:45:21.301362Z 0 [ERROR] [MY-010119] [Server] Aborting
2020-03-21T20:45:21.306253Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.19)  MySQL Community Server - GPL.

innodb_buffer_pool_sizeを超えていることがわかる

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set (0.00 sec)

buffer_pool_sizeを256Mに変更
$ sudo vi /etc/my.cnf

[mysqld]
innodb_buffer_pool_size = 256M

$ sudo service mysqld restart
mysql> SHOW VARIABLES LIKE ‘innodb_buffer_pool_size’;
+————————-+———–+
| Variable_name | Value |
+————————-+———–+
| innodb_buffer_pool_size | 268435456 |
+————————-+———–+
1 row in set (0.02 sec)

これで2~3日様子を見よう。