require 'rdoc/test_case'

=begin
  TODO: test call-seq parsing

/*
 *  call-seq:
 *     ARGF.readlines(sep=$/)     -> array
 *     ARGF.readlines(limit)      -> array
 *     ARGF.readlines(sep, limit) -> array
 *
 *     ARGF.to_a(sep=$/)     -> array
 *     ARGF.to_a(limit)      -> array
 *     ARGF.to_a(sep, limit) -> array
 *
 *  Reads +ARGF+'s current file in its entirety, returning an +Array+ of its
 *  lines, one line per element. Lines are assumed to be separated by _sep_.
 *
 *     lines = ARGF.readlines
 *     lines[0]                #=> "This is line one\n"
 */

assert call-seq did not stop at first empty line

/*
 * call-seq:
 *
 *  flt ** other  ->  float
 *
 * Raises <code>float</code> the <code>other</code> power.
 *
 *    2.0**3      #=> 8.0
 */

assert call-seq correct (bug: was empty)

/* call-seq: flt ** other  ->  float */

assert call-seq correct

=end

class TestRDocParserC < RDoc::TestCase

  def setup
    super

    @tempfile = Tempfile.new self.class.name
    filename = @tempfile.path

    @top_level = @store.add_file filename
    @fn = filename
    @options = RDoc::Options.new
    @options.verbosity = 2
    @stats = RDoc::Stats.new @store, 0
  end

  def teardown
    super

    @tempfile.close
  end

  def test_class_can_parse
    c_parser = RDoc::Parser::C

    temp_dir do
      FileUtils.touch 'file.C'
      assert_equal c_parser, c_parser.can_parse('file.C')

      FileUtils.touch 'file.CC'
      assert_equal c_parser, c_parser.can_parse('file.CC')

      FileUtils.touch 'file.H'
      assert_equal c_parser, c_parser.can_parse('file.H')

      FileUtils.touch 'file.HH'
      assert_equal c_parser, c_parser.can_parse('file.HH')

      FileUtils.touch 'file.c'
      assert_equal c_parser, c_parser.can_parse('file.c')

      FileUtils.touch 'file.cc'
      assert_equal c_parser, c_parser.can_parse('file.cc')

      FileUtils.touch 'file.cpp'
      assert_equal c_parser, c_parser.can_parse('file.cpp')

      FileUtils.touch 'file.cxx'
      assert_equal c_parser, c_parser.can_parse('file.cxx')

      FileUtils.touch 'file.h'
      assert_equal c_parser, c_parser.can_parse('file.h')

      FileUtils.touch 'file.hh'
      assert_equal c_parser, c_parser.can_parse('file.hh')

      FileUtils.touch 'file.y'
      assert_equal c_parser, c_parser.can_parse('file.y')
    end
  end

  def test_initialize
    some_ext        = @top_level.add_class RDoc::NormalClass, 'SomeExt'
                      @top_level.add_class RDoc::SingleClass, 'SomeExtSingle'

    @store.cache[:c_class_variables] = {
      @fn => { 'cSomeExt' => 'SomeExt' }
    }

    @store.cache[:c_singleton_class_variables] = {
      @fn => { 'cSomeExtSingle' => 'SomeExtSingle' }
    }

    parser = RDoc::Parser::C.new @top_level, @fn, '', @options, @stats

    expected = { 'cSomeExt' => some_ext }
    assert_equal expected, parser.classes

    expected = { 'cSomeExtSingle' => 'SomeExtSingle' }
    assert_equal expected, parser.singleton_classes

    expected = {
      'cSomeExt'       => 'SomeExt',
      'cSomeExtSingle' => 'SomeExtSingle',
    }
    known_classes = parser.known_classes.delete_if do |key, _|
      RDoc::KNOWN_CLASSES.keys.include? key
    end

    assert_equal expected, known_classes
  end

  def test_do_attr_rb_attr
    content = <<-EOF
void Init_Blah(void) {
  cBlah = rb_define_class("Blah", rb_cObject);

  /*
   * This is an accessor
   */
  rb_attr(cBlah, rb_intern("accessor"), 1, 1, Qfalse);

  /*
   * This is a reader
   */
  rb_attr(cBlah, rb_intern("reader"), 1, 0, Qfalse);

  /*
   * This is a writer
   */
  rb_attr(cBlah, rb_intern("writer"), 0, 1, Qfalse);
}
    EOF

    klass = util_get_class content, 'cBlah'

    attrs = klass.attributes
    assert_equal 3, attrs.length, attrs.inspect

    accessor = attrs.shift
    assert_equal 'accessor',            accessor.name
    assert_equal 'RW',                  accessor.rw
    assert_equal 'This is an accessor', accessor.comment.text
    assert_equal @top_level,            accessor.file

    reader = attrs.shift
    assert_equal 'reader',           reader.name
    assert_equal 'R',                reader.rw
    assert_equal 'This is a reader', reader.comment.text

    writer = attrs.shift
    assert_equal 'writer',           writer.name
    assert_equal 'W',                writer.rw
    assert_equal 'This is a writer', writer.comment.text
  end

  def test_do_attr_rb_define_attr
    content = <<-EOF
void Init_Blah(void) {
  cBlah = rb_define_class("Blah", rb_cObject);

  /*
   * This is an accessor
   */
  rb_define_attr(cBlah, "accessor", 1, 1);
}
    EOF

    klass = util_get_class content, 'cBlah'

    attrs = klass.attributes
    assert_equal 1, attrs.length, attrs.inspect

    accessor = attrs.shift
    assert_equal 'accessor',            accessor.name
    assert_equal 'RW',                  accessor.rw
    assert_equal 'This is an accessor', accessor.comment.text
    assert_equal @top_level,            accessor.file
  end

  def test_do_aliases
    content = <<-EOF
/*
 * This should show up as an alias with documentation
 */
VALUE blah(VALUE klass, VALUE year) {
}

void Init_Blah(void) {
  cDate = rb_define_class("Date", rb_cObject);

  rb_define_method(cDate, "blah", blah, 1);

  rb_define_alias(cDate, "bleh", "blah");
}
    EOF

    klass = util_get_class content, 'cDate'

    methods = klass.method_list
    assert_equal 2,      methods.length
    assert_equal 'bleh', methods.last.name
    assert_equal 'blah', methods.last.is_alias_for.name

    assert_equal @top_level, methods.last.is_alias_for.file
    assert_equal @top_level, methods.last.file
  end

  def test_do_aliases_singleton
    content = <<-EOF
/*
 * This should show up as a method with documentation
 */
VALUE blah(VALUE klass, VALUE year) {
}

void Init_Blah(void) {
  cDate = rb_define_class("Date", rb_cObject);
  sDate = rb_singleton_class(cDate);

  rb_define_method(sDate, "blah", blah, 1);

  /*
   * This should show up as an alias
   */
  rb_define_alias(sDate, "bleh", "blah");
}
    EOF

    klass = util_get_class content, 'cDate'

    methods = klass.method_list

    assert_equal 2,      methods.length
    assert_equal 'bleh', methods.last.name
    assert               methods.last.singleton
    assert_equal 'blah', methods.last.is_alias_for.name
    assert_equal 'This should show up as an alias', methods.last.comment.text
  end

  def test_do_classes_boot_class
    content = <<-EOF
/* Document-class: Foo
 * this is the Foo boot class
 */
VALUE cFoo = boot_defclass("Foo", rb_cObject);
    EOF

    klass = util_get_class content, 'cFoo'
    assert_equal "this is the Foo boot class", klass.comment.text
    assert_equal 'Object', klass.superclass
  end

  def test_do_classes_boot_class_nil
    content = <<-EOF
/* Document-class: Foo
 * this is the Foo boot class
 */
VALUE cFoo = boot_defclass("Foo", 0);
    EOF

    klass = util_get_class content, 'cFoo'
    assert_equal "this is the Foo boot class", klass.comment.text
    assert_equal nil, klass.superclass
  end

  def test_do_aliases_missing_class
    content = <<-EOF
void Init_Blah(void) {
  rb_define_alias(cDate, "b", "a");
}
    EOF

    _, err = verbose_capture_io do
      refute util_get_class(content, 'cDate')
    end

    assert_equal "Enclosing class or module \"cDate\" for alias b a is not known\n",
                 err
  end

  def test_do_classes_class
    content = <<-EOF
/* Document-class: Foo
 * this is the Foo class
 */
VALUE cFoo = rb_define_class("Foo", rb_cObject);
    EOF

    klass = util_get_class content, 'cFoo'
    assert_equal "this is the Foo class", klass.comment.text
  end

  def test_do_classes_struct
    content = <<-EOF
/* Document-class: Foo
 * this is the Foo class
 */
VALUE cFoo = rb_struct_define_without_accessor(
        "Foo", rb_cObject, foo_alloc,
        "some", "various", "fields", NULL);
    EOF

    klass = util_get_class content, 'cFoo'
    assert_equal "this is the Foo class", klass.comment.text
  end

  def test_do_classes_class_under
    content = <<-EOF
/* Document-class: Kernel::Foo
 * this is the Foo class under Kernel
 */
VALUE cFoo = rb_define_class_under(rb_mKernel, "Foo", rb_cObject);
    EOF

    klass = util_get_class content, 'cFoo'
    assert_equal 'Kernel::Foo', klass.full_name
    assert_equal "this is the Foo class under Kernel", klass.comment.text
  end

  def test_do_classes_class_under_rb_path2class
    content = <<-EOF
/* Document-class: Kernel::Foo
 * this is Kernel::Foo < A::B
 */
VALUE cFoo = rb_define_class_under(rb_mKernel, "Foo", rb_path2class("A::B"));
    EOF

    klass = util_get_class content, 'cFoo'

    assert_equal 'Kernel::Foo', klass.full_name
    assert_equal 'A::B', klass.superclass
    assert_equal 'this is Kernel::Foo < A::B', klass.comment.text
  end

  def test_do_classes_singleton
    content = <<-EOF
VALUE cFoo = rb_define_class("Foo", rb_cObject);
VALUE cFooS = rb_singleton_class(cFoo);
    EOF

    util_get_class content, 'cFooS'

    assert_equal 'Foo', @parser.singleton_classes['cFooS']
  end

  def test_do_classes_module
    content = <<-EOF
/* Document-module: Foo
 * this is the Foo module
 */
VALUE mFoo = rb_define_module("Foo");
    EOF

    klass = util_get_class content, 'mFoo'
    assert_equal "this is the Foo module", klass.comment.text
  end

  def test_do_classes_module_under
    content = <<-EOF
/* Document-module: Kernel::Foo
 * this is the Foo module under Kernel
 */
VALUE mFoo = rb_define_module_under(rb_mKernel, "Foo");
    EOF

    klass = util_get_class content, 'mFoo'
    assert_equal "this is the Foo module under Kernel", klass.comment.text
  end

  def test_do_constants
    content = <<-EOF
#include <ruby.h>

void Init_foo(){
   VALUE cFoo = rb_define_class("Foo", rb_cObject);

   /* 300: The highest possible score in bowling */
   rb_define_const(cFoo, "PERFECT", INT2FIX(300));

   /* Huzzah!: What you cheer when you roll a perfect game */
   rb_define_const(cFoo, "CHEER", rb_str_new2("Huzzah!"));

   /* TEST\:TEST: Checking to see if escaped colon works */
   rb_define_const(cFoo, "TEST", rb_str_new2("TEST:TEST"));

   /* \\: The file separator on MS Windows */
   rb_define_const(cFoo, "MSEPARATOR", rb_str_new2("\\"));

   /* /: The file separator on Unix */
   rb_define_const(cFoo, "SEPARATOR", rb_str_new2("/"));

   /* C:\\Program Files\\Stuff: A directory on MS Windows */
   rb_define_const(cFoo, "STUFF", rb_str_new2("C:\\Program Files\\Stuff"));

   /* Default definition */
   rb_define_const(cFoo, "NOSEMI", INT2FIX(99));

   rb_define_const(cFoo, "NOCOMMENT", rb_str_new2("No comment"));

   /*
    * Multiline comment goes here because this comment spans multiple lines.
    * Multiline comment goes here because this comment spans multiple lines.
    */
   rb_define_const(cFoo, "MULTILINE", INT2FIX(1));

   /*
    * 1: Multiline comment goes here because this comment spans multiple lines.
    * Multiline comment goes here because this comment spans multiple lines.
    */
   rb_define_const(cFoo, "MULTILINE_VALUE", INT2FIX(1));

   /* Multiline comment goes here because this comment spans multiple lines.
    * Multiline comment goes here because this comment spans multiple lines.
    */
   rb_define_const(cFoo, "MULTILINE_NOT_EMPTY", INT2FIX(1));

   /*
    * Multiline comment goes here because this comment spans multiple lines.
    * 1: However, the value extraction should only happen for the first line
    */
   rb_define_const(cFoo, "MULTILINE_COLON_ON_SECOND_LINE", INT2FIX(1));

}
    EOF

    @parser = util_parser content

    @parser.do_classes
    @parser.do_constants

    klass = @parser.classes['cFoo']
    assert klass

    constants = klass.constants
    assert !klass.constants.empty?

    assert_equal @top_level, constants.first.file

    constants = constants.map { |c| [c.name, c.value, c.comment.text] }

    assert_equal ['PERFECT', '300', 'The highest possible score in bowling   '],
                 constants.shift
    assert_equal ['CHEER', 'Huzzah!',
                  'What you cheer when you roll a perfect game   '],
                 constants.shift
    assert_equal ['TEST', 'TEST:TEST',
                  'Checking to see if escaped colon works   '],
                 constants.shift
    assert_equal ['MSEPARATOR', '\\',
                  'The file separator on MS Windows   '],
                 constants.shift
    assert_equal ['SEPARATOR', '/',
                  'The file separator on Unix   '],
                 constants.shift
    assert_equal ['STUFF', 'C:\\Program Files\\Stuff',
                  'A directory on MS Windows   '],
                 constants.shift
    assert_equal ['NOSEMI', 'INT2FIX(99)',
                  'Default definition   '],
                 constants.shift
    assert_equal ['NOCOMMENT', 'rb_str_new2("No comment")', ''],
                 constants.shift

    comment = <<-EOF.chomp
Multiline comment goes here because this comment spans multiple lines.
Multiline comment goes here because this comment spans multiple lines.
    EOF
    assert_equal ['MULTILINE',           'INT2FIX(1)', comment], constants.shift
    assert_equal ['MULTILINE_VALUE',     '1',          comment], constants.shift
    assert_equal ['MULTILINE_NOT_EMPTY', 'INT2FIX(1)', comment], constants.shift

    comment = <<-EOF.chomp
Multiline comment goes here because this comment spans multiple lines.
1: However, the value extraction should only happen for the first line
    EOF
    assert_equal ['MULTILINE_COLON_ON_SECOND_LINE', 'INT2FIX(1)', comment],
                 constants.shift

    assert constants.empty?, constants.inspect
  end

  def test_do_constants_curses
    content = <<-EOF
void Init_curses(){
  mCurses = rb_define_module("Curses");

  /*
   * Document-const: Curses::COLOR_BLACK
   *
   * Value of the color black
   */
  rb_curses_define_const(COLOR_BLACK);
}
    EOF

    @parser = util_parser content

    @parser.do_modules
    @parser.do_classes
    @parser.do_constants

    klass = @parser.classes['mCurses']

    constants = klass.constants
    refute_empty klass.constants

    assert_equal 'COLOR_BLACK', constants.first.name
    assert_equal 'UINT2NUM(COLOR_BLACK)', constants.first.value
    assert_equal 'Value of the color black', constants.first.comment.text
  end

  def test_do_constants_file
    content = <<-EOF
void Init_File(void) {
  /*  Document-const: LOCK_SH
   *
   *  Shared lock
   */
  rb_file_const("LOCK_SH", INT2FIX(LOCK_SH));
}
    EOF

    @parser = util_parser content

    @parser.do_modules
    @parser.do_classes
    @parser.do_constants

    klass = @parser.classes['rb_mFConst']

    constants = klass.constants
    refute_empty klass.constants

    constant = constants.first

    assert_equal 'LOCK_SH',          constant.name
    assert_equal 'INT2FIX(LOCK_SH)', constant.value
    assert_equal 'Shared lock',      constant.comment.text
  end

  def test_do_includes
    content = <<-EOF
Init_foo() {
   VALUE cFoo = rb_define_class("Foo", rb_cObject);
   VALUE mInc = rb_define_module("Inc");

   rb_include_module(cFoo, mInc);
}
    EOF

    klass = util_get_class content, 'cFoo'

    incl = klass.includes.first
    assert_equal 'Inc',      incl.name
    assert_equal '',         incl.comment.text
    assert_equal @top_level, incl.file
  end

  # HACK parsing warning instead of setting up in file
  def test_do_methods_in_c
    content = <<-EOF
VALUE blah(VALUE klass, VALUE year) {
}

void Init_Blah(void) {
  cDate = rb_define_class("Date", rb_cObject);

  rb_define_method(cDate, "blah", blah, 1); /* in blah.c */
}
    EOF

    klass = nil

    _, err = verbose_capture_io do
      klass = util_get_class content, 'cDate'
    end

    assert_match ' blah.c ', err
  end

  # HACK parsing warning instead of setting up in file
  def test_do_methods_in_cpp
    content = <<-EOF
VALUE blah(VALUE klass, VALUE year) {
}

void Init_Blah(void) {
  cDate = rb_define_class("Date", rb_cObject);

  rb_define_method(cDate, "blah", blah, 1); /* in blah.cpp */
}
    EOF

    klass = nil

    _, err = verbose_capture_io do
      klass = util_get_class content, 'cDate'
    end

    assert_match ' blah.cpp ', err
  end

  # HACK parsing warning instead of setting up in file
  def test_do_methods_in_y
    content = <<-EOF
VALUE blah(VALUE klass, VALUE year) {
}

void Init_Blah(void) {
  cDate = rb_define_class("Date", rb_cObject);

  rb_define_method(cDate, "blah", blah, 1); /* in blah.y */
}
    EOF

    klass = nil

    _, err = verbose_capture_io do
      klass = util_get_class content, 'cDate'
    end

    assert_match ' blah.y ', err
  end

  def test_do_methods_singleton_class
    content = <<-EOF
VALUE blah(VALUE klass, VALUE year) {
}

void Init_Blah(void) {
  cDate = rb_define_class("Date", rb_cObject);
  sDate = rb_singleton_class(cDate);

  rb_define_method(sDate, "blah", blah, 1);
}
    EOF

    klass = util_get_class content, 'cDate'

    methods = klass.method_list
    assert_equal 1,      methods.length
    assert_equal 'blah', methods.first.name
    assert               methods.first.singleton
  end

  def test_do_missing
    parser = util_parser

    klass_a = @top_level.add_class RDoc::ClassModule, 'A'
    parser.classes['a'] = klass_a

    parser.enclosure_dependencies['c'] << 'b'
    parser.enclosure_dependencies['b'] << 'a'
    parser.enclosure_dependencies['d'] << 'a'

    parser.missing_dependencies['d'] = ['d', :class, 'D', 'Object', 'a']
    parser.missing_dependencies['c'] = ['c', :class, 'C', 'Object', 'b']
    parser.missing_dependencies['b'] = ['b', :class, 'B', 'Object', 'a']

    parser.do_missing

    assert_equal %w[A A::B A::B::C A::D],
                 @store.all_classes_and_modules.map { |m| m.full_name }.sort
  end

  def test_do_missing_cycle
    parser = util_parser

    klass_a = @top_level.add_class RDoc::ClassModule, 'A'
    parser.classes['a'] = klass_a

    parser.enclosure_dependencies['c'] << 'b'
    parser.enclosure_dependencies['b'] << 'a'

    parser.missing_dependencies['c'] = ['c', :class, 'C', 'Object', 'b']
    parser.missing_dependencies['b'] = ['b', :class, 'B', 'Object', 'a']

    parser.enclosure_dependencies['y'] << 'z'
    parser.enclosure_dependencies['z'] << 'y'

    parser.missing_dependencies['y'] = ['y', :class, 'Y', 'Object', 'z']
    parser.missing_dependencies['z'] = ['z', :class, 'Z', 'Object', 'y']

    _, err = verbose_capture_io do
      parser.do_missing
    end

    expected = 'Unable to create class Y (y), class Z (z) ' +
               'due to a cyclic class or module creation'

    assert_equal expected, err.chomp

    assert_equal %w[A A::B A::B::C],
                 @store.all_classes_and_modules.map { |m| m.full_name }.sort
  end

  def test_find_alias_comment
    parser = util_parser

    comment = parser.find_alias_comment 'C', '[]', 'index'

    assert_equal '', comment.text

    parser = util_parser <<-C
/*
 * comment
 */

rb_define_alias(C, "[]", "index");
    C

    comment = parser.find_alias_comment 'C', '[]', 'index'

    assert_equal "/*\n * comment\n */\n\n", comment.text
  end

  def test_find_class_comment
    @options.rdoc_include << File.dirname(__FILE__)

    content = <<-EOF
/*
 * Comment 1
 */
foo = rb_define_class("MyClassName1", rb_cObject);

/*
 * Comment 2
 */
bar = rb_define_class("MyClassName2", rb_cObject);
    EOF

    util_get_class content

    assert_equal "Comment 1", @parser.classes['foo'].comment.text
    assert_equal "Comment 2", @parser.classes['bar'].comment.text
  end

  def test_find_class_comment_init
    content = <<-EOF
/*
 * a comment for class Foo
 */
void
Init_Foo(void) {
  VALUE foo = rb_define_class("Foo", rb_cObject);
}
    EOF

    klass = util_get_class content, 'foo'

    assert_equal "a comment for class Foo", klass.comment.text
  end

  def test_find_class_comment_define_class
    content = <<-EOF
/*
 * a comment for class Foo
 */
VALUE foo = rb_define_class("Foo", rb_cObject);
    EOF

    klass = util_get_class content, 'foo'

    assert_equal "a comment for class Foo", klass.comment.text
  end

  def test_find_class_comment_define_class_Init_Foo
    content = <<-EOF
/*
 * a comment for class Foo on Init
 */
void
Init_Foo(void) {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE foo = rb_define_class("Foo", rb_cObject);
}
    EOF

    klass = util_get_class content, 'foo'

    assert_equal "a comment for class Foo on Init", klass.comment.text
  end

  def test_find_class_comment_define_class_Init_Foo_no_void
    content = <<-EOF
/*
 * a comment for class Foo on Init
 */
void
Init_Foo() {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE foo = rb_define_class("Foo", rb_cObject);
}
    EOF

    klass = util_get_class content, 'foo'

    assert_equal "a comment for class Foo on Init", klass.comment.text
  end

  def test_find_class_comment_define_class_bogus_comment
    content = <<-EOF
/*
 * a comment for other_function
 */
void
other_function() {
}

void
Init_Foo(void) {
    VALUE foo = rb_define_class("Foo", rb_cObject);
}
    EOF

    klass = util_get_class content, 'foo'

    assert_equal '', klass.comment.text
  end

  def test_find_class_comment_define_class_under
    content = <<-EOF
/*
 * a comment for class Foo
 */
VALUE foo = rb_define_class_under(rb_cObject, "Foo", rb_cObject);
    EOF

    klass = util_get_class content, 'foo'

    assert_equal "a comment for class Foo", klass.comment.text
  end

  def test_find_class_comment_define_class_under_Init
    content = <<-EOF
/*
 * a comment for class Foo on Init
 */
void
Init_Foo(void) {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE foo = rb_define_class_under(rb_cObject, "Foo", rb_cObject);
}
    EOF

    klass = util_get_class content, 'foo'

    # the inner comment is used since Object::Foo is not necessarily the same
    # thing as "Foo" for Init_
    assert_equal "a comment for class Foo on rb_define_class",
                 klass.comment.text
  end

  def test_find_const_comment_rb_define
    content = <<-EOF
/*
 * A comment
 */
rb_define_const(cFoo, "CONST", value);
    EOF

    parser = util_parser content

    comment = parser.find_const_comment 'const', 'CONST'

    assert_equal "/*\n * A comment\n */\n", comment.text
  end

  def test_find_const_comment_document_const
    content = <<-EOF
/*
 * Document-const: CONST
 *
 * A comment
 */
    EOF

    parser = util_parser content

    comment = parser.find_const_comment nil, 'CONST'

    assert_equal "/*\n *\n * A comment\n */", comment.text
  end

  def test_find_const_comment_document_const_full_name
    content = <<-EOF
/*
 * Document-const: Foo::CONST
 *
 * A comment
 */
    EOF

    parser = util_parser content

    comment = parser.find_const_comment nil, 'CONST', 'Foo'

    assert_equal "/*\n *\n * A comment\n */", comment.text
  end

  def test_find_body
    content = <<-EOF
/*
 * a comment for other_function
 */
VALUE
other_function() {
}

void
Init_Foo(void) {
    VALUE foo = rb_define_class("Foo", rb_cObject);

    rb_define_method(foo, "my_method", other_function, 0);
}
    EOF

    klass = util_get_class content, 'foo'
    other_function = klass.method_list.first

    assert_equal 'my_method', other_function.name
    assert_equal "a comment for other_function",
                 other_function.comment.text
    assert_equal '()', other_function.params

    code = other_function.token_stream.first.text

    assert_equal "VALUE\nother_function() {\n}", code
  end

  def test_find_body_2
    content = <<-CONTENT
/* Copyright (C) 2010  Sven Herzberg
 *
 * This file is free software; the author(s) gives unlimited
 * permission to copy and/or distribute it, with or without
 * modifications, as long as this notice is preserved.
 */

#include <ruby.h>

static VALUE
wrap_initialize (VALUE  self)
{
  return self;
}

/* */
static VALUE
wrap_shift (VALUE self,
            VALUE arg)
{
  return self;
}

void
init_gi_repository (void)
{
  VALUE mTest = rb_define_module ("Test");
  VALUE cTest = rb_define_class_under (mTest, "Test", rb_cObject);

  rb_define_method (cTest, "initialize", wrap_initialize, 0);
  rb_define_method (cTest, "shift", wrap_shift, 0);
}
    CONTENT

    klass = util_get_class content, 'cTest'
    assert_equal 2, klass.method_list.length
  end

  def test_find_body_cast
    content = <<-EOF
/*
 * a comment for other_function
 */
VALUE
other_function() {
}

void
Init_Foo(void) {
    VALUE foo = rb_define_class("Foo", rb_cObject);

    rb_define_method(foo, "my_method", (METHOD)other_function, 0);
}
    EOF

    klass = util_get_class content, 'foo'
    other_function = klass.method_list.first

    assert_equal 'my_method', other_function.name
    assert_equal "a comment for other_function",
                 other_function.comment.text
    assert_equal '()', other_function.params

    code = other_function.token_stream.first.text

    assert_equal "VALUE\nother_function() {\n}", code
  end

  def test_find_body_define
    content = <<-EOF
#define something something_else

#define other_function rb_other_function

/*
 * a comment for rb_other_function
 */
VALUE
rb_other_function() {
}

void
Init_Foo(void) {
    VALUE foo = rb_define_class("Foo", rb_cObject);

    rb_define_method(foo, "my_method", other_function, 0);
}
    EOF

    klass = util_get_class content, 'foo'
    other_function = klass.method_list.first

    assert_equal 'my_method', other_function.name
    assert_equal 'a comment for rb_other_function', other_function.comment.text
    assert_equal '()', other_function.params
    assert_equal 118, other_function.offset
    assert_equal 8, other_function.line

    code = other_function.token_stream.first.text

    assert_equal "VALUE\nrb_other_function() {\n}", code
  end

  def test_find_body_define_comment
    content = <<-EOF
/*
 * a comment for other_function
 */
#define other_function rb_other_function

/* */
VALUE
rb_other_function() {
}

void
Init_Foo(void) {
    VALUE foo = rb_define_class("Foo", rb_cObject);

    rb_define_method(foo, "my_method", other_function, 0);
}
    EOF

    klass = util_get_class content, 'foo'
    other_function = klass.method_list.first

    assert_equal 'my_method', other_function.name
    assert_equal 'a comment for other_function', other_function.comment.text
    assert_equal '()', other_function.params
    assert_equal 39, other_function.offset
    assert_equal 4, other_function.line

    code = other_function.token_stream.first.text

    assert_equal "#define other_function rb_other_function", code
  end

  def test_find_body_document_method
    content = <<-EOF
/*
 * Document-method: bar
 * Document-method: baz
 *
 * a comment for bar
 */
VALUE
bar() {
}

void
Init_Foo(void) {
    VALUE foo = rb_define_class("Foo", rb_cObject);

    rb_define_method(foo, "bar", bar, 0);
    rb_define_method(foo, "baz", bar, 0);
}
    EOF

    klass = util_get_class content, 'foo'
    assert_equal 2, klass.method_list.length

    methods = klass.method_list.sort

    bar = methods.first
    assert_equal 'Foo#bar', bar.full_name
    assert_equal "a comment for bar", bar.comment.text

    baz = methods.last
    assert_equal 'Foo#baz', baz.full_name
    assert_equal "a comment for bar", baz.comment.text
  end

  def test_find_body_document_method_equals
    content = <<-EOF
/*
 * Document-method: Zlib::GzipFile#mtime=
 *
 * A comment
 */
static VALUE
rb_gzfile_set_mtime(VALUE obj, VALUE mtime)
{

void
Init_zlib() {
    mZlib = rb_define_module("Zlib");
    cGzipFile = rb_define_class_under(mZlib, "GzipFile", rb_cObject);
    cGzipWriter = rb_define_class_under(mZlib, "GzipWriter", cGzipFile);
    rb_define_method(cGzipWriter, "mtime=", rb_gzfile_set_mtime, 1);
}
    EOF

    klass = util_get_class content, 'cGzipWriter'
    assert_equal 1, klass.method_list.length

    methods = klass.method_list.sort

    bar = methods.first
    assert_equal 'Zlib::GzipWriter#mtime=', bar.full_name
    assert_equal 'A comment', bar.comment.text
  end

  def test_find_body_document_method_same
    content = <<-EOF
VALUE
s_bar() {
}

VALUE
bar() {
}

/*
 * Document-method: Foo::bar
 *
 * a comment for Foo::bar
 */

/*
 * Document-method: Foo#bar
 *
 * a comment for Foo#bar
 */

void
Init_Foo(void) {
    VALUE foo = rb_define_class("Foo", rb_cObject);

    rb_define_singleton_method(foo, "bar", s_bar, 0);
    rb_define_method(foo, "bar", bar, 0);
}
    EOF

    klass = util_get_class content, 'foo'
    assert_equal 2, klass.method_list.length

    methods = klass.method_list.sort

    s_bar = methods.first
    assert_equal 'Foo::bar', s_bar.full_name
    assert_equal "a comment for Foo::bar", s_bar.comment.text

    bar = methods.last
    assert_equal 'Foo#bar', bar.full_name
    assert_equal "a comment for Foo#bar", bar.comment.text
  end

  def test_find_modifiers_call_seq
    comment = RDoc::Comment.new <<-COMMENT
call-seq:
  commercial() -> Date <br />

If no arguments are given:

    COMMENT

    parser = util_parser
    method_obj = RDoc::AnyMethod.new nil, 'blah'

    parser.find_modifiers comment, method_obj

    expected = <<-CALL_SEQ.chomp
commercial() -> Date <br />

    CALL_SEQ

    assert_equal expected, method_obj.call_seq
  end

  def test_find_modifiers_nodoc
    comment = RDoc::Comment.new <<-COMMENT
/* :nodoc:
 *
 * Blah
 */

    COMMENT

    parser = util_parser
    method_obj = RDoc::AnyMethod.new nil, 'blah'

    parser.find_modifiers comment, method_obj

    assert_equal nil, method_obj.document_self
  end

  def test_find_modifiers_yields
    comment = RDoc::Comment.new <<-COMMENT
/* :yields: a, b
 *
 * Blah
 */

    COMMENT

    parser = util_parser
    method_obj = RDoc::AnyMethod.new nil, 'blah'

    parser.find_modifiers comment, method_obj

    assert_equal 'a, b', method_obj.block_params

    assert_equal "\n\nBlah", comment.text
  end

  def test_handle_method_args_minus_1
    parser = util_parser "Document-method: Object#m\n blah */"

    parser.content = <<-BODY
VALUE
rb_other(VALUE obj) {
  rb_funcall(obj, rb_intern("other"), 0);
  return rb_str_new2("blah, blah, blah");
}

VALUE
rb_m(int argc, VALUE *argv, VALUE obj) {
  VALUE o1, o2;
  rb_scan_args(argc, argv, "1", &o1, &o2);
}
    BODY

    parser.handle_method 'method', 'rb_cObject', 'm', 'rb_m', -1

    m = @top_level.find_module_named('Object').method_list.first

    assert_equal 'm', m.name
    assert_equal @top_level, m.file
    assert_equal 115, m.offset
    assert_equal 7, m.line

    assert_equal '(p1)', m.params
  end

  def test_handle_method_args_0
    parser = util_parser "Document-method: BasicObject#==\n blah */"

    parser.handle_method 'method', 'rb_cBasicObject', '==', 'rb_obj_equal', 0

    bo = @top_level.find_module_named 'BasicObject'

    assert_equal 1, bo.method_list.length

    equals2 = bo.method_list.first

    assert_equal '()', equals2.params
  end

  def test_handle_method_args_1
    parser = util_parser "Document-method: BasicObject#==\n blah */"

    parser.handle_method 'method', 'rb_cBasicObject', '==', 'rb_obj_equal', 1

    bo = @top_level.find_module_named 'BasicObject'

    assert_equal 1, bo.method_list.length

    equals2 = bo.method_list.first

    assert_equal '(p1)', equals2.params
  end

  def test_handle_method_args_2
    parser = util_parser "Document-method: BasicObject#==\n blah */"

    parser.handle_method 'method', 'rb_cBasicObject', '==', 'rb_obj_equal', 2

    bo = @top_level.find_module_named 'BasicObject'

    assert_equal 1, bo.method_list.length

    equals2 = bo.method_list.first

    assert_equal '(p1, p2)', equals2.params
  end

  # test_handle_args_minus_1 handled by test_handle_method

  def test_handle_method_args_minus_2
    parser = util_parser "Document-method: BasicObject#==\n blah */"

    parser.handle_method 'method', 'rb_cBasicObject', '==', 'rb_obj_equal', -2

    bo = @top_level.find_module_named 'BasicObject'

    assert_equal 1, bo.method_list.length

    equals2 = bo.method_list.first

    assert_equal '(*args)', equals2.params
  end

  def test_handle_method_initialize
    parser = util_parser "Document-method: BasicObject::new\n blah */"

    parser.handle_method('private_method', 'rb_cBasicObject',
                         'initialize', 'rb_obj_dummy', -1)

    bo = @top_level.find_module_named 'BasicObject'

    assert_equal 1, bo.method_list.length

    new = bo.method_list.first

    assert_equal 'new',   new.name
    assert_equal :public, new.visibility
  end

  def test_handle_singleton
    parser = util_parser <<-SINGLE
void Init_Blah(void) {
  cDate = rb_define_class("Date", rb_cObject);
  sDate = rb_singleton_class(cDate);
}
    SINGLE

    parser.scan

    assert_equal 'Date', parser.known_classes['sDate']
    assert_equal 'Date', parser.singleton_classes['sDate']
  end

  def test_look_for_directives_in
    parser = util_parser

    comment = RDoc::Comment.new "# :other: not_handled\n"

    parser.look_for_directives_in @top_level, comment

    assert_equal "# :other: not_handled\n", comment.text
    assert_equal 'not_handled', @top_level.metadata['other']
  end

  def test_load_variable_map
    some_ext = @top_level.add_class RDoc::NormalClass, 'SomeExt'
               @top_level.add_class RDoc::NormalClass, 'OtherExt'

    @store.cache[:c_class_variables][@fn]       = { 'cSomeExt'  => 'SomeExt'  }
    @store.cache[:c_class_variables]['other.c'] = { 'cOtherExt' => 'OtherExt' }

    parser = util_parser

    map = parser.load_variable_map :c_class_variables

    expected = { 'cSomeExt' => some_ext }

    assert_equal expected, map

    assert_equal 'SomeExt', parser.known_classes['cSomeExt']
    assert_nil              parser.known_classes['cOtherExt']
  end

  def test_load_variable_map_empty
    parser = util_parser

    map = parser.load_variable_map :c_class_variables

    assert_empty map
  end

  def test_load_variable_map_legacy
    @store.cache[:c_class_variables] = nil

    parser = util_parser

    map = parser.load_variable_map :c_class_variables

    assert_empty map
  end

  def test_load_variable_map_singleton
    @top_level.add_class RDoc::NormalClass, 'SomeExt'
    @top_level.add_class RDoc::NormalClass, 'OtherExt'

    @store.cache[:c_singleton_class_variables][@fn] =
      { 'cSomeExt'  => 'SomeExt'  }
    @store.cache[:c_singleton_class_variables]['other.c'] =
      { 'cOtherExt' => 'OtherExt' }

    parser = util_parser

    map = parser.load_variable_map :c_singleton_class_variables

    expected = { 'cSomeExt' => 'SomeExt' }

    assert_equal expected, map

    assert_equal 'SomeExt', parser.known_classes['cSomeExt']
    assert_nil              parser.known_classes['cOtherExt']
  end

  def test_load_variable_map_trim
    a = @top_level.add_class RDoc::NormalClass, 'A'

    @store.cache[:c_class_variables][@fn] = {
      'cA'  => 'A',
      'cB'  => 'B',
    }

    parser = util_parser

    map = parser.load_variable_map :c_class_variables

    expected = { 'cA' => a }

    assert_equal expected, map
  end

  def test_define_method
    content = <<-EOF
/*Method Comment! */
static VALUE
rb_io_s_read(argc, argv, io)
    int argc;
    VALUE *argv;
    VALUE io;
{
}

void
Init_IO(void) {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE rb_cIO = rb_define_class("IO", rb_cObject);
    rb_define_singleton_method(rb_cIO, "read", rb_io_s_read, -1);
}
    EOF

    klass = util_get_class content, 'rb_cIO'
    read_method = klass.method_list.first
    assert_equal "read", read_method.name
    assert_equal "Method Comment!   ", read_method.comment.text
    assert_equal "rb_io_s_read", read_method.c_function
    assert read_method.singleton
  end

  def test_define_method_with_prototype
    content = <<-EOF
static VALUE rb_io_s_read(int, VALUE*, VALUE);

/* Method Comment! */
static VALUE
rb_io_s_read(argc, argv, io)
    int argc;
    VALUE *argv;
    VALUE io;
{
}

void
Init_IO(void) {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE rb_cIO = rb_define_class("IO", rb_cObject);
    rb_define_singleton_method(rb_cIO, "read", rb_io_s_read, -1);
}
    EOF

    klass = util_get_class content, 'rb_cIO'
    read_method = klass.method_list.first
    assert_equal "read", read_method.name
    assert_equal "Method Comment!   ", read_method.comment.text
    assert_equal "rb_io_s_read", read_method.c_function
    assert read_method.singleton
  end

  def test_define_method_private
    content = <<-EOF
/*Method Comment! */
static VALUE
rb_io_s_read(argc, argv, io)
    int argc;
    VALUE *argv;
    VALUE io;
{
}

void
Init_IO(void) {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE rb_cIO = rb_define_class("IO", rb_cObject);
    rb_define_private_method(rb_cIO, "read", rb_io_s_read, -1);
}
    EOF

    klass = util_get_class content, 'rb_cIO'
    read_method = klass.method_list.first
    assert_equal 'IO#read', read_method.full_name
    assert_equal :private, read_method.visibility
    assert_equal "Method Comment!   ", read_method.comment.text
  end

  def test_define_method_private_singleton
    content = <<-EOF
/*Method Comment! */
static VALUE
rb_io_s_read(argc, argv, io)
    int argc;
    VALUE *argv;
    VALUE io;
{
}

void
Init_IO(void) {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE rb_cIO = rb_define_class("IO", rb_cObject);
    VALUE rb_cIO_s = rb_singleton_class(rb_cIO);
    rb_define_private_method(rb_cIO_s, "read", rb_io_s_read, -1);
}
    EOF

    klass = util_get_class content, 'rb_cIO'
    read_method = klass.method_list.first
    assert_equal "read", read_method.name
    assert_equal "Method Comment!   ", read_method.comment.text
    assert_equal :private, read_method.visibility
    assert read_method.singleton
  end

  def test_define_method_singleton
    content = <<-EOF
/*Method Comment! */
static VALUE
rb_io_s_read(argc, argv, io)
    int argc;
    VALUE *argv;
    VALUE io;
{
}

void
Init_IO(void) {
    /*
     * a comment for class Foo on rb_define_class
     */
    VALUE rb_cIO = rb_define_class("IO", rb_cObject);
    VALUE rb_cIO_s = rb_singleton_class(rb_cIO);
    rb_define_method(rb_cIO_s, "read", rb_io_s_read, -1);
}
    EOF

    klass = util_get_class content, 'rb_cIO'
    read_method = klass.method_list.first
    assert_equal "read", read_method.name
    assert_equal "Method Comment!   ", read_method.comment.text
    assert read_method.singleton
  end

  def test_rb_scan_args
    parser = util_parser

    assert_equal '(p1)',
                 parser.rb_scan_args('rb_scan_args(a, b, "1",)')
    assert_equal '(p1, p2)',
                 parser.rb_scan_args('rb_scan_args(a, b, "2",)')

    assert_equal '(p1 = v1)',
                 parser.rb_scan_args('rb_scan_args(a, b, "01",)')
    assert_equal '(p1 = v1, p2 = v2)',
                 parser.rb_scan_args('rb_scan_args(a, b, "02",)')

    assert_equal '(p1, p2 = v2)',
                 parser.rb_scan_args('rb_scan_args(a, b, "11",)')

    assert_equal '(p1, *args)',
                 parser.rb_scan_args('rb_scan_args(a, b, "1*",)')
    assert_equal '(p1, p2 = {})',
                 parser.rb_scan_args('rb_scan_args(a, b, "1:",)')
    assert_equal '(p1, &block)',
                 parser.rb_scan_args('rb_scan_args(a, b, "1&",)')

    assert_equal '(p1, p2)',
                 parser.rb_scan_args('rb_scan_args(a, b, "101",)')

    assert_equal '(p1, p2 = v2, p3)',
                 parser.rb_scan_args('rb_scan_args(a, b, "111",)')

    assert_equal '(p1, *args, p3)',
                 parser.rb_scan_args('rb_scan_args(a, b, "1*1",)')

    assert_equal '(p1, p2 = v2, *args)',
                 parser.rb_scan_args('rb_scan_args(a, b, "11*",)')
    assert_equal '(p1, p2 = v2, p3 = {})',
                 parser.rb_scan_args('rb_scan_args(a, b, "11:",)')
    assert_equal '(p1, p2 = v2, &block)',
                 parser.rb_scan_args('rb_scan_args(a, b, "11&",)')

    assert_equal '(p1, p2 = v2, *args, p4, p5 = {}, &block)',
                 parser.rb_scan_args('rb_scan_args(a, b, "11*1:&",)')

    # The following aren't valid according to spec but are according to the
    # implementation.
    assert_equal '(*args)',
                 parser.rb_scan_args('rb_scan_args(a, b, "*",)')
    assert_equal '(p1 = {})',
                 parser.rb_scan_args('rb_scan_args(a, b, ":",)')
    assert_equal '(&block)',
                 parser.rb_scan_args('rb_scan_args(a, b, "&",)')

    assert_equal '(*args, p2 = {})',
                 parser.rb_scan_args('rb_scan_args(a, b, "*:",)')
    assert_equal '(p1 = {}, &block)',
                 parser.rb_scan_args('rb_scan_args(a, b, ":&",)')
    assert_equal '(*args, p2 = {}, &block)',
                 parser.rb_scan_args('rb_scan_args(a, b, "*:&",)')
  end

  def test_scan
    parser = util_parser <<-C
void Init(void) {
    mM = rb_define_module("M");
    cC = rb_define_class("C", rb_cObject);
    sC = rb_singleton_class(cC);
}
    C

    parser.scan

    expected = {
      @fn => {
        'mM' => 'M',
        'cC' => 'C', }}
    assert_equal expected, @store.c_class_variables

    expected = {
      @fn => {
        'sC' => 'C' } }
    assert_equal expected, @store.c_singleton_class_variables
  end

  def test_scan_method_copy
    parser = util_parser <<-C
/*
 *  call-seq:
 *    pathname.to_s    -> string
 *    pathname.to_path -> string
 *
 *  Return the path as a String.
 *
 *  to_path is implemented so Pathname objects are usable with File.open, etc.
 */
static VALUE
path_to_s(VALUE self) { }

/*
 *  call-seq:
 *     str[index]               -> new_str or nil
 *     str[start, length]       -> new_str or nil
 *     str.slice(index)         -> new_str or nil
 *     str.slice(start, length) -> new_str or nil
 */
static VALUE
path_aref_m(int argc, VALUE *argv, VALUE str) { }

/*
 *  call-seq:
 *     string <=> other_string   -> -1, 0, +1 or nil
 */
static VALUE
path_cmp_m(VALUE str1, VALUE str2) { }

/*
 *  call-seq:
 *     str == obj    -> true or false
 *     str === obj   -> true or false
 */
VALUE
rb_str_equal(VALUE str1, VALUE str2) { }

Init_pathname()
{
    rb_cPathname = rb_define_class("Pathname", rb_cObject);

    rb_define_method(rb_cPathname, "to_s",    path_to_s, 0);
    rb_define_method(rb_cPathname, "to_path", path_to_s, 0);
    rb_define_method(rb_cPathname, "[]",      path_aref_m, -1);
    rb_define_method(rb_cPathname, "slice",   path_aref_m, -1);
    rb_define_method(rb_cPathname, "<=>",     path_cmp_m, 1);
    rb_define_method(rb_cPathname, "==",      rb_str_equal), 2);
    rb_define_method(rb_cPathname, "===",     rb_str_equal), 2);
}
    C

    parser.scan

    pathname = @store.classes_hash['Pathname']

    to_path = pathname.method_list.find { |m| m.name == 'to_path' }
    assert_equal "pathname.to_path -> string", to_path.call_seq

    to_s = pathname.method_list.find { |m| m.name == 'to_s' }
    assert_equal "pathname.to_s    -> string", to_s.call_seq

    index_expected = <<-EXPECTED.chomp
str[index]               -> new_str or nil
str[start, length]       -> new_str or nil
    EXPECTED

    index = pathname.method_list.find { |m| m.name == '[]' }
    assert_equal index_expected, index.call_seq, '[]'

    slice_expected = <<-EXPECTED.chomp
str.slice(index)         -> new_str or nil
str.slice(start, length) -> new_str or nil
    EXPECTED

    slice = pathname.method_list.find { |m| m.name == 'slice' }
    assert_equal slice_expected, slice.call_seq

    spaceship = pathname.method_list.find { |m| m.name == '<=>' }
    assert_equal "string <=> other_string   -> -1, 0, +1 or nil",
                 spaceship.call_seq

    equals2 = pathname.method_list.find { |m| m.name == '==' }
    assert_match 'str == obj', equals2.call_seq

    equals3 = pathname.method_list.find { |m| m.name == '===' }
    assert_match 'str === obj', equals3.call_seq
  end

  def test_scan_order_dependent
    parser = util_parser <<-C
void a(void) {
    mA = rb_define_module("A");
}

void b(void) {
    cB = rb_define_class_under(mA, "B", rb_cObject);
}

void c(void) {
    mC = rb_define_module_under(cB, "C");
}

void d(void) {
    mD = rb_define_class_under(mC, "D");
}
    C

    parser.scan

    assert_equal %w[A A::B A::B::C],
                 @store.all_classes_and_modules.map { |m| m.full_name }.sort
  end

  def util_get_class content, name = nil
    @parser = util_parser content
    @parser.scan

    @parser.classes[name] if name
  end

  def util_parser content = ''
    RDoc::Parser::C.new @top_level, @fn, content, @options, @stats
  end

end

