aboutsummaryrefslogtreecommitdiffstats
path: root/tests/check_irc.c
blob: 66fe00214f690c1916da4813fb111263203d70ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdlib.h>
#include <glib.h>
#include <gmodule.h>
#include <check.h>
#include <string.h>
#include <stdio.h>
#include "irc.h"
#include "testsuite.h"

START_TEST(test_connect)
	GIOChannel *ch1, *ch2;
	irc_t *irc;
	char *raw;
	fail_unless(g_io_channel_pair(&ch1, &ch2));

	irc = irc_new(g_io_channel_unix_get_fd(ch1));

	irc_free(irc);

	fail_unless(g_io_channel_read_to_end(ch2, &raw, NULL, NULL) == G_IO_STATUS_NORMAL);
	
	fail_if(strcmp(raw, "") != 0);

	g_free(raw);
END_TEST

START_TEST(test_login)
	GIOChannel *ch1, *ch2;
	irc_t *irc;
	GError *error = NULL;
	char *raw;
	fail_unless(g_io_channel_pair(&ch1, &ch2));

	g_io_channel_set_flags(ch1, G_IO_FLAG_NONBLOCK, NULL);
	g_io_channel_set_flags(ch2, G_IO_FLAG_NONBLOCK, NULL);

	irc = irc_new(g_io_channel_unix_get_fd(ch1));

	fail_unless(g_io_channel_write_chars(ch2, "NICK bla\r\r\n"
			"USER a a a a\n", -1, NULL, NULL) == G_IO_STATUS_NORMAL);
	fail_unless(g_io_channel_flush(ch2, NULL) == G_IO_STATUS_NORMAL);

	g_main_iteration(FALSE);
	irc_free(irc);

	fail_unless(g_io_channel_read_to_end(ch2, &raw, NULL, NULL) == G_IO_STATUS_NORMAL);
	
	fail_unless(strstr(raw, "001") != NULL);
	fail_unless(strstr(raw, "002") != NULL);
	fail_unless(strstr(raw, "003") != NULL);
	fail_unless(strstr(raw, "004") != NULL);
	fail_unless(strstr(raw, "005") != NULL);

	g_free(raw);
END_TEST

Suite *irc_suite (void)
{
	Suite *s = suite_create("IRC");
	TCase *tc_core = tcase_create("Core");
	suite_add_tcase (s, tc_core);
	tcase_add_test (tc_core, test_connect);
	tcase_add_test (tc_core, test_login);
	return s;
}
id='n203' href='#n203'>203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
require 'thread'
require 'rdoc/code_object'

##
# A Context is something that can hold modules, classes, methods, attributes,
# aliases, requires, and includes. Classes, modules, and files are all
# Contexts.

class RDoc::Context < RDoc::CodeObject

  ##
  # Types of methods

  TYPES = %w[class instance]

  ##
  # Method visibilities

  VISIBILITIES = [:public, :protected, :private]

  ##
  # Aliased methods

  attr_reader :aliases

  ##
  # attr* methods

  attr_reader :attributes

  ##
  # Constants defined

  attr_reader :constants

  ##
  # Current section of documentation

  attr_reader :current_section

  ##
  # Files this context is found in

  attr_reader :in_files

  ##
  # Modules this context includes

  attr_reader :includes

  ##
  # Methods defined in this context

  attr_reader :method_list

  ##
  # Name of this class excluding namespace.  See also full_name

  attr_reader :name

  ##
  # Files this context requires

  attr_reader :requires

  ##
  # Sections in this context

  attr_reader :sections

  ##
  # Aliases that haven't been resolved to a method

  attr_accessor :unmatched_alias_lists

  ##
  # Current visibility of this context

  attr_reader :visibility

  ##
  # A per-comment section of documentation like:
  #
  #   # :SECTION: The title
  #   # The body

  class Section

    ##
    # Section comment

    attr_reader :comment

    ##
    # Context this Section lives in

    attr_reader :parent

    ##
    # Section sequence number (for linking)

    attr_reader :sequence

    ##
    # Section title

    attr_reader :title

    @@sequence = "SEC00000"
    @@sequence_lock = Mutex.new

    ##
    # Creates a new section with +title+ and +comment+

    def initialize(parent, title, comment)
      @parent = parent
      @title = title

      @@sequence_lock.synchronize do
        @@sequence.succ!
        @sequence = @@sequence.dup
      end

      set_comment comment
    end

    ##
    # Sections are equal when they have the same #sequence

    def ==(other)
      self.class === other and @sequence == other.sequence
    end

    def inspect # :nodoc:
      "#<%s:0x%x %s %p>" % [
        self.class, object_id,
        @sequence, title
      ]
    end

    ##
    # Set the comment for this section from the original comment block If
    # the first line contains :section:, strip it and use the rest.
    # Otherwise remove lines up to the line containing :section:, and look
    # for those lines again at the end and remove them. This lets us write
    #
    #   # blah blah blah
    #   #
    #   # :SECTION: The title
    #   # The body

    def set_comment(comment)
      return unless comment

      if comment =~ /^#[ \t]*:section:.*\n/ then
        start = $`
        rest = $'

        if start.empty?
          @comment = rest
        else
          @comment = rest.sub(/#{start.chomp}\Z/, '')
        end
      else
        @comment = comment
      end

      @comment = nil if @comment.empty?
    end

  end

  ##
  # Creates an unnamed empty context with public visibility

  def initialize
    super

    @in_files = []

    @name    ||= "unknown"
    @comment ||= ""
    @parent  = nil
    @visibility = :public

    @current_section = Section.new self, nil, nil
    @sections = [@current_section]

    initialize_methods_etc
    initialize_classes_and_modules
  end

  ##
  # Sets the defaults for classes and modules

  def initialize_classes_and_modules
    @classes = {}
    @modules = {}
  end

  ##
  # Sets the defaults for methods and so-forth

  def initialize_methods_etc
    @method_list = []
    @attributes  = []
    @aliases     = []
    @requires    = []
    @includes    = []
    @constants   = []

    # This Hash maps a method name to a list of unmatched aliases (aliases of
    # a method not yet encountered).
    @unmatched_alias_lists = {}
  end

  ##
  # Contexts are sorted by full_name

  def <=>(other)
    full_name <=> other.full_name
  end

  ##
  # Adds +an_alias+ that is automatically resolved

  def add_alias(an_alias)
    meth = find_instance_method_named(an_alias.old_name)

    if meth then
      add_alias_impl an_alias, meth
    else
      add_to @aliases, an_alias
      unmatched_alias_list = @unmatched_alias_lists[an_alias.old_name] ||= []
      unmatched_alias_list.push an_alias
    end

    an_alias
  end

  ##
  # Adds +an_alias+ pointing to +meth+

  def add_alias_impl(an_alias, meth)
    new_meth = RDoc::AnyMethod.new an_alias.text, an_alias.new_name
    new_meth.is_alias_for = meth
    new_meth.singleton    = meth.singleton
    new_meth.params       = meth.params
    new_meth.comment = "Alias for \##{meth.name}"
    meth.add_alias new_meth
    add_method new_meth
  end

  ##
  # Adds +attribute+

  def add_attribute(attribute)
    add_to @attributes, attribute
  end

  ##
  # Adds a class named +name+ with +superclass+.
  #
  # Given <tt>class Container::Item</tt> RDoc assumes +Container+ is a module
  # unless it later sees <tt>class Container</tt>.  add_class automatically
  # upgrades +name+ to a class in this case.

  def add_class(class_type, name, superclass = 'Object')
    klass = add_class_or_module @classes, class_type, name, superclass

    # If the parser encounters Container::Item before encountering
    # Container, then it assumes that Container is a module.  This may not
    # be the case, so remove Container from the module list if present and
    # transfer any contained classes and modules to the new class.

    RDoc::TopLevel.lock.synchronize do
      mod = RDoc::TopLevel.modules_hash.delete klass.full_name

      if mod then
        klass.classes_hash.update mod.classes_hash
        klass.modules_hash.update mod.modules_hash
        klass.method_list.concat mod.method_list

        @modules.delete klass.name
      end

      RDoc::TopLevel.classes_hash[klass.full_name] = klass
    end

    klass
  end

  ##
  # Instantiates a +class_type+ named +name+ and adds it the modules or
  # classes Hash +collection+.

  def add_class_or_module(collection, class_type, name, superclass = nil)
    full_name = if RDoc::TopLevel === self then # HACK
                  name
                else
                  "#{self.full_name}::#{name}"
                end
    mod = collection[name]

    if mod then
      mod.superclass = superclass unless mod.module?
    else
      all = nil

      RDoc::TopLevel.lock.synchronize do
        all = if class_type == RDoc::NormalModule then
                RDoc::TopLevel.modules_hash
              else
                RDoc::TopLevel.classes_hash
              end

        mod = all[full_name]
      end

      unless mod then
        mod = class_type.new name, superclass
      else
        # If the class has been encountered already, check that its
        # superclass has been set (it may not have been, depending on the
        # context in which it was encountered).
        if class_type == RDoc::NormalClass then
          mod.superclass = superclass unless mod.superclass
        end
      end

      unless @done_documenting then
        RDoc::TopLevel.lock.synchronize do
          all[full_name] = mod
        end
        collection[name] = mod
      end

      mod.section = @current_section
      mod.parent = self
    end

    mod
  end

  ##
  # Adds +constant+

  def add_constant(constant)
    add_to @constants, constant
  end

  ##
  # Adds included module +include+

  def add_include(include)
    add_to @includes, include
  end

  ##
  # Adds +method+

  def add_method(method)
    method.visibility = @visibility
    add_to @method_list, method

    unmatched_alias_list = @unmatched_alias_lists[method.name]
    if unmatched_alias_list then
      unmatched_alias_list.each do |unmatched_alias|
        add_alias_impl unmatched_alias, method
        @aliases.delete unmatched_alias
      end

      @unmatched_alias_lists.delete method.name
    end
  end

  ##
  # Adds a module named +name+.  If RDoc already knows +name+ is a class then
  # that class is returned instead.  See also #add_class

  def add_module(class_type, name)
    return @classes[name] if @classes.key? name

    add_class_or_module @modules, class_type, name, nil
  end

  ##
  # Adds +require+ to this context's top level

  def add_require(require)
    if RDoc::TopLevel === self then
      add_to @requires, require
    else
      parent.add_require require
    end
  end

  ##
  # Adds +thing+ to the collection +array+

  def add_to(array, thing)
    array << thing if @document_self and not @done_documenting
    thing.parent = self
    thing.section = @current_section
  end

  ##
  # Array of classes in this context

  def classes
    @classes.values
  end

  ##
  # All classes and modules in this namespace

  def classes_and_modules
    classes + modules
  end

  ##
  # Hash of classes keyed by class name

  def classes_hash
    @classes
  end

  ##
  # Is part of this thing was defined in +file+?

  def defined_in?(file)
    @in_files.include?(file)
  end

  ##
  # Iterator for attributes

  def each_attribute # :yields: attribute
    @attributes.each {|a| yield a}
  end

  ##
  # Iterator for classes and modules

  def each_classmodule(&block) # :yields: module
    classes_and_modules.sort.each(&block)
  end

  ##
  # Iterator for constants

  def each_constant # :yields: constant
    @constants.each {|c| yield c}
  end

  ##
  # Iterator for included modules

  def each_include # :yields: include
    @includes.each do |i| yield i end
  end

  ##
  # Iterator for methods

  def each_method # :yields: method
    @method_list.sort.each {|m| yield m}
  end

  ##
  # Finds an attribute with +name+ in this context

  def find_attribute_named(name)
    @attributes.find { |m| m.name == name }
  end

  ##
  # Finds a constant with +name+ in this context

  def find_constant_named(name)
    @constants.find {|m| m.name == name}
  end

  ##
  # Find a module at a higher scope

  def find_enclosing_module_named(name)
    parent && parent.find_module_named(name)
  end

  ##
  # Finds a file with +name+ in this context

  def find_file_named(name)
    top_level.class.find_file_named(name)
  end

  ##
  # Finds an instance method with +name+ in this context

  def find_instance_method_named(name)
    @method_list.find { |meth| meth.name == name && !meth.singleton }
  end

  ##
  # Finds a method, constant, attribute, module or files named +symbol+ in
  # this context

  def find_local_symbol(symbol)
    find_method_named(symbol) or
    find_constant_named(symbol) or
    find_attribute_named(symbol) or
    find_module_named(symbol) or
    find_file_named(symbol)
  end

  ##
  # Finds a instance or module method with +name+ in this context

  def find_method_named(name)
    @method_list.find { |meth| meth.name == name }
  end

  ##
  # Find a module with +name+ using ruby's scoping rules

  def find_module_named(name)
    res = @modules[name] || @classes[name]
    return res if res
    return self if self.name == name
    find_enclosing_module_named name
  end

  ##
  # Look up +symbol+.  If +method+ is non-nil, then we assume the symbol
  # references a module that contains that method.

  def find_symbol(symbol, method = nil)
    result = nil

    case symbol
    when /^::(.*)/ then
      result = top_level.find_symbol($1)
    when /::/ then
      modules = symbol.split(/::/)

      unless modules.empty? then
        module_name = modules.shift
        result = find_module_named(module_name)

        if result then
          modules.each do |name|
            result = result.find_module_named name
            break unless result
          end
        end
      end

    else
      # if a method is specified, then we're definitely looking for
      # a module, otherwise it could be any symbol
      if method then
        result = find_module_named symbol
      else
        result = find_local_symbol symbol
        if result.nil? then
          if symbol =~ /^[A-Z]/ then
            result = parent
            while result && result.name != symbol do
              result = result.parent
            end
          end
        end
      end
    end

    if result and method then
      fail unless result.respond_to? :find_local_symbol
      result = result.find_local_symbol(method)
    end

    result
  end

  ##
  # URL for this with a +prefix+

  def http_url(prefix)
    path = full_name
    path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<</
    path = [prefix] + path.split('::')

    File.join(*path.compact) + '.html'
  end

  ##
  # Breaks method_list into a nested hash by type (class or instance) and
  # visibility (public, protected private)

  def methods_by_type
    methods = {}

    TYPES.each do |type|
      visibilities = {}
      VISIBILITIES.each do |vis|
        visibilities[vis] = []
      end

      methods[type] = visibilities
    end

    each_method do |method|
      methods[method.type][method.visibility] << method
    end

    methods
  end

  ##
  # Yields Method and Attr entries matching the list of names in +methods+.
  # Attributes are only returned when +singleton+ is false.

  def methods_matching(methods, singleton = false)
    count = 0

    @method_list.each do |m|
      if methods.include? m.name and m.singleton == singleton then
        yield m
        count += 1
      end
    end

    return if count == methods.size || singleton

    @attributes.each do |a|
      yield a if methods.include? a.name
    end
  end

  ##
  # Array of modules in this context

  def modules
    @modules.values
  end

  ##
  # Hash of modules keyed by module name

  def modules_hash
    @modules
  end

  ##
  # Changes the visibility for new methods to +visibility+

  def ongoing_visibility=(visibility)
    @visibility = visibility
  end

  ##
  # Record which file +top_level+ is in

  def record_location(top_level)
    @in_files << top_level unless @in_files.include?(top_level)
  end

  ##
  # If a class's documentation is turned off after we've started collecting
  # methods etc., we need to remove the ones we have

  def remove_methods_etc
    initialize_methods_etc
  end

  ##
  # Given an array +methods+ of method names, set the visibility of each to
  # +visibility+

  def set_visibility_for(methods, visibility, singleton = false)
    methods_matching methods, singleton do |m|
      m.visibility = visibility
    end
  end

  ##
  # Removes classes and modules when we see a :nodoc: all

  def remove_classes_and_modules
    initialize_classes_and_modules
  end

  ##
  # Creates a new section with +title+ and +comment+

  def set_current_section(title, comment)
    @current_section = Section.new self, title, comment
    @sections << @current_section
  end

  ##
  # Return the TopLevel that owns us

  def top_level
    return @top_level if defined? @top_level
    @top_level = self
    @top_level = @top_level.parent until RDoc::TopLevel === @top_level
    @top_level
  end

end