2011年9月27日火曜日

Twitter謎の仕様について

久々にブログを書いているんだが…

Twitterの謎の仕様について


書いてみようと思います。

はるかぜちゃん(@harukazechan)っていますね。
彼女がツイートに顔を表象するときの記号を皆さんご存知だと思います。

(ω)

それを真似しようと思って、こんなツイートを書いてみました。

(m)


するとツイッターは何事もなかったのような反応を示しました。

おや、と思って、何度かやり直しましたが、全く反応なし。
そこで、普通にツイートしてみると、ツイートされる。

これは何だろうと思って、いろいろと試してみました。
そうすると、

(w)

や、

(M)

もダメなようでした。

そこでGroovyですよ


総当りで何がダメなのかを調べてみました。

ProhibitedTweet.groovy

@Grab(group='org.twitter4j', module='twitter4j-core', version='2.2.4')
import twitter4j.*
import twitter4j.auth.*
import twitter4j.conf.*

class Result {
    int id
    long status
    String expected
    String result
    String showResult(){
        return "success : ${expected == result}, id : ${id}, expected : ${expected}, result : ${result}"
    }
    @Override
    String toString() {
        return "<id : ${id}, expected : ${expected}, result : ${result}, status : ${status}>"
    }
}

def consumerKey = 'consumerKey'
def consumerSecret = 'consumerSecret'
def accessToken = 'accessToken'
def accessTokenSecret = 'accessTokenSecret'

def conf =
    new ConfigurationBuilder()
        .setOAuthConsumerKey(consumerKey)
        .setOAuthConsumerSecret(consumerSecret)
        .setOAuthAccessToken(accessToken)
        .setOAuthAccessTokenSecret(accessTokenSecret).build()
def twitter = new TwitterFactory(conf).getInstance()

def results = []
(65..90).each {
    def tweets = "(${it as char})"
    try{
        def result = twitter.updateStatus(tweets)
        results << new Result(id : it, expected : tweets, result : result.text, status : result.id)
    }catch(TwitterException e) {
        results << new Result(id : it, expected : tweets, result : '', status : -1)
    }
}

results.each { println it.showResult()}


実行結果は、こんなになりました。


success : true, id : 65, expected : (A), result : (A)
success : true, id : 66, expected : (B), result : (B)
success : true, id : 67, expected : (C), result : (C)
success : false, id : 68, expected : (D), result : (C)
success : true, id : 69, expected : (E), result : (E)
success : false, id : 70, expected : (F), result : (E)
success : false, id : 71, expected : (G), result : (E)
success : true, id : 72, expected : (H), result : (H)
success : true, id : 73, expected : (I), result : (I)
success : true, id : 74, expected : (J), result : (J)
success : true, id : 75, expected : (K), result : (K)
success : false, id : 76, expected : (L), result : (K)
success : false, id : 77, expected : (M), result : (K)
success : true, id : 78, expected : (N), result : (N)
success : true, id : 79, expected : (O), result : (O)
success : true, id : 80, expected : (P), result : (P)
success : true, id : 81, expected : (Q), result : (Q)
success : true, id : 82, expected : (R), result : (R)
success : false, id : 83, expected : (S), result : (R)
success : true, id : 84, expected : (T), result : (T)
success : true, id : 85, expected : (U), result : (U)
success : true, id : 86, expected : (V), result : (V)
success : false, id : 87, expected : (W), result : (V)
success : true, id : 88, expected : (X), result : (X)
success : true, id : 89, expected : (Y), result : (Y)
success : true, id : 90, expected : (Z), result : (Z)


結果としては、

(D)、(F)、(G)、(L)、(M)、(S)、(W)、(d)、(f)、(g)、(l)、(m)、(s)、(w)

を単独でツイートすることはできないということがわかりました。
これらをツイートすると、前のツイーとが返ってきます。

考察

さて、理由ですが、わかりません。
fとか、sはなんとなくわかりますが、
他のが全然わかりません。
(fはf**k、sはs*x)

顔文字とか、何かを想起するような記号なのかと考えましたが、
顔文字とかのように、文字によって何かを表すというのは日本語や中国語の表意文字体系での発想であり、
英語圏というかラテン語圏というか、インド何とか諸語の系統の表音文字体系にはない発想ですので、
それを禁止しているようにも思えません。

というわけで、なんでダメなんでしょうね。


2011年9月11日日曜日

Gradleでスローテスト問題を解決するAgain

前回、こんな記事書きました。
見事に効果がわからないという結果が出てきて、どうやればいいのか考えていましたが、
さすがGroovyクラスタに素晴らしい先輩がいらっしゃいました。

@bluepapa32 先輩です。
単純に、スリープするコード


    for (int i = 0; i < 100; i++ ) {
        Thread.sleep(10);
    }


を埋め込めばよかったわけですね。

というわけで、こんなテスト生成スクリプトでテストを大量に作ってみることにしました。


CreateTest.groovy

import static groovyx.gpars.GParsPool.*;
def packagePath = 'C:/Users/mike/IDEA_Project/GradleSample/src/test/java/orz/mikeneck/gradle/sample/boxunbox/test'

def head = $/
package orz.mikeneck.gradle.sample.boxunbox.test;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/$

def body = $/
    public static final int SIZE = 100;
    private List<Integer> intList;
    private List<Long> longList;
    @Test
    public void testInteger()   throws InterruptedException {
        int[] array = new int[SIZE];
        int position = 0;
        for(Integer item : intList) {
            array[position++] = item;
            Thread.sleep(10);
        }
        for (int i : array)
            assertThat(i, is(intList.get(i)));
    }
    @Test
    public void testLong()  throws InterruptedException {
        long[] array = new long[SIZE];
        int position = 0;
        for (Long item : longList) {
            array[position++] = item;
            Thread.sleep(10);
        }
        position = 0;
        for(long item : array)
            assertThat(item, is(longList.get(position++)));
    }
    @Before
    public void setUp() throws Exception {
        Integer[] integers = new Integer[SIZE];
        Long[] longs = new Long[SIZE];
        for(int i = 0; i < SIZE; i++)
            integers[i] = new Integer(i);
        intList = Arrays.asList(integers);
        for (int i = 0; i < SIZE; i++)
            longs[i] = new Long(i + Integer.MAX_VALUE);
        longList = Arrays.asList(longs);
    }
}
/$

def numbers = []
(1..300).each {
    numbers << it
}

withPool {
    numbers.collectParallel { number ->
        def className = "BoxUnboxTest${number}"
        def name = "${className}.java"
        def fileName = "${packagePath}/${name}"
        def define = "public class ${className} {"
        def content = new StringWriter()
        content << head
        content << define
        content << body
        println ' ---- '
        println "now processing : $fileName"
        println ' ---- '
        new File(fileName).write(content.toString(), 'UTF-8')
        assert new File(fileName).exists() == true
    }
}


10msecを100回Sleepするテストメソッドが二つで、ひとつのテストの実行にかかる時間は2sec。
これが300個あるので、必要な時間は600sec = 10min かかるテストです。

実際に一つテストを実施してみました。


2secかかっていますね。
これが300個用意されるので、10minかかることが想定されます。

では、次のGradleスクリプトで実行してみましょう。


build.gradle

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.8.2'
}



なお、マシンの環境は次のような感じです。
OS : Windows 7
CPU : Intel Xeon X3460 (8Core) (64bit)
RAM : 8.00GB




では、実行、というかその間に色々と他のことで負荷がかからないように、
IntelliJ IDEAが起動していて、このブログだけがChrome上で起動しているという状態にしておきます。
で、IntelliJのRunツールで実行するのではなく、ふつうにコマンドから起動します。

さて、オレはその間、『JOJOの奇妙な冒険』を読むこととします。

では、いざ実行…




c:\Users\mike\IdeaProjects\TestingGradle>gradle build
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Total time: 15 mins 42.983 secs


15分43秒、マジ遅え。
しかも、最初の数個目までのテストは結構サクサク進んでいたけど、200を越えたあたりからテストの実行速度が落ちてきた感じがする。
こういう時はJVMの再起動などが必要なのかもしれん。

というわけで、さっそく、並列処理を試してみようと思う。

build.gradle

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.8.2'
}

test {
    maxParallelForks = 6
    forkEvery = 30
}



前回にも書きましたが、
  • maxParallelForksは並列するスレッド数。
  • forkEveryは指定した回数テストを実行するとJVMを再起動して、OutOfMemoryExceptionを回避する仕組みです。

では、実行開始です。


c:\Users\mike\IdeaProjects\TestingGradle>gradle build
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test
:check
:build

BUILD SUCCESSFUL

Total time: 2 mins 42.544 secs




えっ、2分Σ(゚д゚lll



めちゃくちゃ早くなっていません?
元々15分43秒かかっていたのがたったの2分43秒。

単純に計算すると

15min43sec / 6(core) = 2min37sec


となるから、妥当な値ですね。

では、テストのサマリーを比較してみましょう。



並列化前



並列化後



これを見るとCPU時間は変わっていません。
つまり、CPU時間をすべて複数コアで実行したために早くすることが出来たという事になります。

というわけで、結論

GradlemaxParallelForksを使うともれなくスローテスト問題が解決できる。