Персональный блог Злого Рубиста

Ошибка при установке CoffeeScript в Windows и как ее бороть

  •  
December 22, 2011 18:238 коммент.

Устанавливал себе в винду CoffeeScript согласно ману на офсайте.

После запуска команды npm install -g coffee-script

появлялось сообщение npm http GET https://registry.npmjs.org/coffee-script и все надолго останавливалось.

Несколько раз прекращал по Ctrl-C, а в последний раз вывалился стек сообщений об ошибках. Оказалось, решение простое - указать npm-у где лежит реестр (приложений?):

npm config set registry http://registry.npmjs.org/

После этого установилось:

C:\InstantRails\ruby_apps\coffee_script>npm --version
1.1.0-beta-4

C:\InstantRails\ruby_apps\coffee_script>npm install -g coffee-script
npm http GET https://registry.npmjs.org/coffee-script
npm ERR! Error: failed to fetch from registry: coffee-script
npm ERR!     at C:\Program Files\nodejs\node_modules\npm\lib\utils\npm-registry-client\get.js:139:12
npm ERR!     at cb (C:\Program Files\nodejs\node_modules\npm\lib\utils\npm-registry-client\request.js:32:9)
npm ERR!     at Request._callback (C:\Program Files\nodejs\node_modules\npm\lib\utils\npm-registry-client\request.js:137:18)
npm ERR!     at Request.callback (C:\Program Files\nodejs\node_modules\npm\node_modules\request\main.js:104:22)
npm ERR!     at Request.<anonymous> (C:\Program Files\nodejs\node_modules\npm\node_modules\request\main.js:181:58)
npm ERR!     at Request.emit (events.js:88:20)
npm ERR!     at ClientRequest.<anonymous> (C:\Program Files\nodejs\node_modules\npm\node_modules\request\main.js:178:10)
npm ERR!     at ClientRequest.emit (events.js:67:17)
npm ERR!     at CleartextStream.<anonymous> (http.js:1174:13)
npm ERR!     at CleartextStream.emit (events.js:88:20)
npm ERR! Report this *entire* log at:
npm ERR!     <http://github.com/isaacs/npm/issues>
npm ERR! or email it to:
npm ERR!     <npm-@googlegroups.com>
npm ERR!
npm ERR! System Windows_NT 5.1.2600
npm ERR! command "C:\\Program Files\\nodejs\\\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "instal
l" "-g" "coffee-script"
npm ERR! cwd C:\InstantRails\ruby_apps\coffee_script
npm ERR! node -v v0.6.6
npm ERR! npm -v 1.1.0-beta-4
npm ERR! message failed to fetch from registry: coffee-script
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR!     C:\InstantRails\ruby_apps\coffee_script\npm-debug.log
npm not ok

C:\InstantRails\ruby_apps\coffee_script>npm config set registry http://registry.npmjs.org/

C:\InstantRails\ruby_apps\coffee_script>npm install -g coffee-script
npm http GET http://registry.npmjs.org/coffee-script
npm http 200 http://registry.npmjs.org/coffee-script
npm http GET http://registry.npmjs.org/coffee-script/-/coffee-script-1.2.0.tgz
npm http 200 http://registry.npmjs.org/coffee-script/-/coffee-script-1.2.0.tgz
C:\Documents and Settings\Sony\Application Data\npm\coffee -> C:\Documents and Settings\Sony\Application Data\npm\node_modules\c
offee-script\bin\coffee
C:\Documents and Settings\Sony\Application Data\npm\cake -> C:\Documents and Settings\Sony\Application Data\npm\node_modules\cof
fee-script\bin\cake
coffee-script@1.2.0 C:\Documents and Settings\Sony\Application Data\npm\node_modules\coffee-script

C:\InstantRails\ruby_apps\coffee_script>

Сохранение контекста блока при использовании yield

  •  
December 17, 2011 01:091 коммент.

Наткнулся в коде на два способа вызова блока в методе, куда он передан: yield и instance_eval. Зацените:

class A
    def do_smth opts={}, &block
        if opts[:yield_self]
            yield self
        else
            instance_eval &block
        end
    end
end

a = A.new
a.do_smth :yield_self => false do
  puts "self: %s"%self.inspect
end

a.do_smth :yield_self => true  do
  puts "self: %s"%self.inspect
end

Выводит в консоль:

C:\InstantRails\ruby_apps\yield>ruby test_yield.rb

self: #\<A:0x2852688\>
self: main

Вывод такой - yield выполняет блок в контексте, внешнем по отношению к методу, в котором  вызывается yield, а instance_eval выполняет блок непосредственно в контексте того метода, к котором вызывается сам instance_eval.

Тестирование коммуникационной части на основе eventmachine при помощи сucumber

  •  
4.0 из 1 гол.
November 18, 2011 18:374 коммент.

Например, хочется подвергнуть cucumber-тестированию не только бизнес логику, но и коммуникационную часть.

Примерно так:

# language: ru
Функционал: Коммуникация через socket_connection

    Чтобы быть уверенным, что сервер получает от socket_connection правильные
        объекты, когда socket_connection получает из сокета правильные строки

    Контекст: Поднят сервер EventMachine
        Допустим используется протокол "DataExchange::ProtocolXml"
            И поднят сервер EventMachine на порту "10001"
   
    Сценарий: Приходит строка connect, должно быть создано событие
        Если от клиента приходит строка "<command token='connect' key='секретный ключ'/>"
            То сокет драйвер передает серверу событие:
                |атрибут|значение                  |
                |class  |DataExchange::TokenConnect|
                |key    |секретный ключ            |

    @wip
    Сценарий: Приходит строка с raise, должно сгенерироваться событие
        Если от клиента приходит строка "<command token='raise' key='x' amount=100/>"
            То сокет драйвер передает серверу событие:
                |атрибут|значение                  |
                |class  |DataExchange::TokenRaise  |
                |key    |x                         |
                |amount |100                       |

Если коммуникационная часть построена на основе EventMachine, то должен быть задействован основной цикл реагирования EM:

EventMachine.run do
    ...
end

Запускаем его в параллельной нити. Эта нить завершится как только завершится основная нить с тестами.

Допустим /^поднят сервер EventMachine на порту "(.*)"$/ do |port_str|
    @port = port_str.to_i
    Thread.new do
        EventMachine.run do
            puts "EventMachine called"
            # hit Control + C to stop
            Signal.trap("INT")  { EventMachine.stop }
            Signal.trap("TERM") { EventMachine.stop }
        end
    end
    EventMachine::start_server( "0.0.0.0", @port, SocketConnection )
end

Вот шаги steps для использования в тестах:

Допустим /^подключается клиент$/ do #"
    @socket ||= TCPSocket.open( 'localhost', @port )
end

Если /^от клиента приходит строка "(.*)"$/ do |data_str| #"
    Допустим 'подключается клиент'
    @socket<< data_str # отправили в сокет
    sleep 0.1          # дать пакету время пройти стек протоколов
                       # и время другой нити для приема пакета
end

То /^сокет драйвер передает серверу событие:$/ do |table|
    # создать событие из данных в таблице
    hash = {}
    table.hashes.each do |item|
        hash[item["атрибут"]] = item["значение"]
    end

    # тут в hash доступны параметры, указанные в таблице в тесте

    # мой код, а будет ваш ->
    token = eval "#{hash['class']}.new"
    token.set_attributes! hash

    # <-
   
    #сравнить с тем, что получил сервер
    @mock_server.events.shift.should == token
end

О применении cucumber для тестирования не web приложения.

  •  
October 06, 2011 12:432 коммент.

Cucumber - превосходный фреймворк для тестирования web-приложения. Он поддерживает Rails, Sinatra, Merb.

Я хочу попробовать приспособить его для тестирования серверного не web приложения. А точнее для тестирования самописного игрового сервера Texas Holdem. Суть эксперимента состоит в том чтобы тестировать только движок без коммуникационной части, чтобы можно было писать примерно такие сценарии:

# language: ru
Функционал: Работа с блайндами в Техасском Холдеме

  Чтобы протестировать работу с блайндами
  Являясь разработчиком, использующим cucumber
  Я хочу быть уверенным, что блайнды назначаются корректно

  Сценарий: Дилер наместе 1, блайнды на 2,3
    Допустим создана игра для игроков:
        | место | никнейм  | стек  |
        |    1  | Иван     | 20000 |
        |    2  | Роман    | 20000 |
        |    3  | Дэн      | 20000 |
        |    4  | Злой     | 20000 |
        |    5  | Абель    | 20000 |
    И дилер Иван
    И сумма малого блайнда 1
    Если на малом блайнде Роман
    То на большом блайнде Дэн
      И сумма малого блайнда 1
      И сумма большого блайнда 2
      И стек игрока Иван 20000
      И стек игрока Роман 19999
      И стек игрока Дэн 19998
      И стек игрока Злой 20000
      И стек игрока Абель 20000.0
      И ожидается ход игрока Злой

Чтобы реализовать это, планировал разбираться с геммой aruba, надеясь там почерпнуть вдохновение.

Вдохновение почерпнул не из aruba, а из примера calculator, приложенного к cucumber (cucumber-1.1.0\examples\i18n\ru\features\step_definitions\calculator_steps.rb)

Пришлось в ходе исследования немного переписать сценарий. Дело в том, что создать игру можно только когда известно на каких местах сидят игроки, с какими стеками, кто дилер и каков размер малого блайнда.

Исходный Модифицированный
     Допустим создана игра для игроков:
        | место | никнейм  | стек  |
        |    1  | Иван     | 20000 |
        |    2  | Роман    | 20000 |
        |    3  | Дэн      | 20000 |
        |    4  | Злой     | 20000 |
        |    5  | Абель    | 20000 |
    И дилер Иван
    И сумма малого блайнда 1

 
     Допустим за столом сидят игроки:
        | место | никнейм  | стек  |
        |   1   | Иван     | 20000 |
        |   2   | Роман    | 20000 |
        |   3   | Дэн      | 20000 |
        |   4   | Злой     | 20000 |
        |   5   | Абель    | 20000 |
    Если создана игра, где дилер Иван, а сумма малого блайнда 2

 
 Видно, что в исходном сценарии только в строке И сумма малого блайнда 1 становится известен последний необходимый параметр. Поэтому строфа Допустим создана игра ... должна идти после строфы И сумма малого блайнда 1.
 
 В модифицированном сценарии сначала задается список игроков, а затем создается игра с передачей необходимых ей параметров.

 

Таким образом, если есть желание протестировать вашу суперовую логику, то начинаете именно с написания теста. Для начала всего одного, вот например с такого
    Сценарий: Дилер на месте 1, блайнды на 2,1 и всего двое игроков
        Допустим за столом сидят игроки:
            | место | никнейм | стек  |
            |   1   | Иван    | 20000 |
            |   2   | Роман   | 20000 |
        Если создана игра, где дилер Иван, а сумма малого блайнда 1
        То на малом блайнде Роман
            А на большом блайнде Иван
            И сумма малого блайнда 1
            И сумма большого блайнда 2
            И стек игрока Иван 19998
            И стек игрока Роман 19999
            И ожидается ход игрока Роман

И помещаете его в файл PROJECT_ROOT/features/blinds.feature. Также создаете папки, которые ожидает найти кукумбер:
PROJECT_ROOT/features/support
PROJECT_ROOT/features/step_definitions


Создаете файл PROJECT_ROOT/features/support/env.rb с таким содержанием:

# encoding: utf-8
begin require 'rspec/expectations'; rescue LoadError; require 'spec/expectations'; end
require 'cucumber/formatter/unicode'
$:.unshift(File.dirname(__FILE__) + '/../../lib')

И в корневой папке проекта запускаете cucumber. Получаете такой  вывод:

Копируете весь вывод после фразы: You can implement step definitions for undefined steps with these snippets:
в файл PROJECT_ROOT/features/step_definitions/blinds_steps.rb Это заготовка наших шагов:


Затем надо их реализовать один за другим, запуская кук после реализации каждого метода.Причем кол-во шагов должно сократиться за счет однотипных:
То /^стек игрока Иван (\d+)$/ do |arg1|
То /^стек игрока Роман (\d+)$/ do |arg1|

Получится примерно так:
require File.expand_path(File.join(File.dirname(__FILE__),  '../../lib/server_config_test'))
require File.expand_path(File.join(File.dirname(__FILE__),  '../../lib/texas_holdem'))
GameServer::ServerConfig.TestConfigBlackHole

Допустим /^за столом сидят игроки:$/ do |table|
    @players= []
    id = 0
    table.hashes.each do |item|
        seat         = item['место'].to_i
        nickname     = item['никнейм']
        stack_amount = item['стек'].to_i
        player = PokerServer::Player.new(nickname, id += 1).take_seat( seat, stack_amount )
        @players<< player
    end
end

Если /^создана игра, где дилер Иван, а сумма малого блайнда (\d+)$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

То /^на малом блайнде (.*)$/ do | player_nickname |
  pending # express the regexp above with the code you wish you had
end

То /^на большом блайнде (.*)$/ do | player_nickname |
  pending # express the regexp above with the code you wish you had
end

То /^сумма малого блайнда (\d+)$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

То /^сумма большого блайнда (\d+)$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

То /^стек игрока (.*) (\d+)$/ do | player_nickname, stack_amount |
  pending # express the regexp above with the code you wish you had
end

То /^ожидается ход игрока (.*)$/ do | player_nickname |
  pending # express the regexp above with the code you wish you had
end


После реализации первого шага вывод изменится:


Один шаг успешно пройден! После реализации всех шагов картинка станет такой:

Что получается?

Чтобы тестировать произвольное приложение, достаточно определить использованные в сценариях тестирования шаги в rb-файлах в папке PROJECT_ROOT/features/step_definitions. И все, вот так просто!

ps
Кук целесообразно использовать для тестирования тех компонентов, которые содержат основную, так называемую "бизнес-логику", описать которую эффективно получится на человеческом языке, а какие-нибудь вспомогательные классы, работу которых запаришься описывать словами, целесообразнее тестировать TestUnit-ом.

 

pps
если в сценарии заключать параметры шагов в кавычки, то заготовки шагов, которые генерирует кук, будут с учетом параметров.

        То на малом блайнде "Роман"
            А на большом блайнде Иван"
            И сумма малого блайнда "1"
            И сумма большого блайнда "2"
            И стек игрока "Иван" "19998"
            И стек игрока "Роман" "19999"
            И ожидается ход "игрока" "Роман"

Простое средство для создания гуевой (GUI) оболочки к консольной утилите

  •  
August 01, 2011 21:350 коммент.

На случай, если вашу полезную консольную утилиту надо по быстрому адаптировать к гуевому интерфейсу, в Linux нашел спецовое средство - zenity. В винде тоже есть.

Это консольный утил, который получает параметры и рисует пользователю определенный параметрами Диалог. Диалогами могут быть запросы текста, вывод соообщений/предупреждений/ошибок, выбор даты, выбор значения из списка, и т.п. - все стандартные элементы интерфейса, можно даже выводить прогресс бар.

Выбранное пользователем значение zenity печатает в stdout.

Можно забацать уютный класс-обертку, а можно использовать совсем просто:

input_date_str = `zenity --calendar --text Введите\ дату\ документа`

Это позволяет супер просто реализовать мастер ввода параметров вашей полезной утилиты.

Конечно, у такого подхода есть и недостатки - последовательный показ нескольких диалогов, вместо одного насыщенного элементами диалога, невозможность проверить введенные значения непосредственно в диалоге, мелькание диалогов, если параметров надо ввести более 3-х. Но в простых случаях вполне юзабельно.

Скриншот zenity-диалога календарь