android / ndk

The Android Native Development Kit
1.99k stars 257 forks source link

document coverage workflow #1120

Open kong1z opened 4 years ago

kong1z commented 4 years ago

Expected scenario:

Compile a *.so with ndk and integrate it into the apk. Expect to generate a .gcda file when running the app on Android, and then analyze the code coverage of c++.

NDK version: ndk-r10d

Ndk compile *.so operation:

Set the compile link parameters as follows:

// Android.mk
LOCAL_CFLAGS += -fprofile-arcs -ftest-coverage
LOCAL_CXXFLAGS += -fprofile-arcs -ftest-coverage
LOCAL_LDFLAGS += -lgcov -lm -llog -landroid -lgcc

Set the path where .gcda is stored (the path can be written):

// Test.cc
Setenv("GCOV_PREFIX", "/data/local/tmp", 1);
Setenv("GCOV_PREFIX_STRIP", "100", 1);

The .gcno file is generated normally when compiling and generating so. Then I integrated *.so into the Android apk.

There is information on the Internet that the program should be completed normally and cannot be killed. So I force __gcov_flush() to be called, and I make sure this function is called.

The end result is that the .gcda file cannot be generated after the program is run on an Android phone.

So, does ndk support code coverage? If it is supported, where is the problem?

I have nowhere else to find useful information. Looking forward to answering, thank you.

DanAlbert commented 4 years ago

NDK version: ndk-r10d

I have no idea how that's supposed to behave. Try again with r20 and lmk if you have problems. AFAIK coverage works with the process you described (though iirc GCOV_PREFIX_STRIP depends on your build location, so that may need to be tweaked).

kong1z commented 4 years ago

NDK version: ndk-r10d

I have no idea how that's supposed to behave. Try again with r20 and lmk if you have problems. AFAIK coverage works with the process you described (though iirc GCOV_PREFIX_STRIP depends on your build location, so that may need to be tweaked).

Configuration:

LOCAL_CFLAGS += -fprofile-arcs -ftest-coverage
LOCAL_CXXFLAGS += -fprofile-arcs -ftest-coverage
LOCAL_LDLIBS += --coverage

I used ndk-r20, but still don't generate .gcda files after the android app finishes running. I can't analyze code coverage without .gcda files.

Looks like the Android app links .so files, unable to analyze the code coverage of .so files. I don't know if it's a problem with ndk, maybe ndk doesn't have this feature.

In addition, what is lmk?

enh-google commented 4 years ago

@pirama-arumuga-nainar is the expert on code coverage on Android...

("lmk" is short for "let me know".)

pirama-arumuga-nainar commented 4 years ago

The profiles are written during exit() via an atexit callback. But Android apps usually get killed by the framework and don't get to run the atexit callbacks. The app should instead call llvm_gcov_flush() to explicitly write the coverage files to disk. You may also need to set GCOV_PREFIX to some path that's accessible/writable from the app.

kong1z commented 4 years ago

The profiles are written during exit() via an atexit callback. But Android apps usually get killed by the framework and don't get to run the atexit callbacks. The app should instead call llvm_gcov_flush() to explicitly write the coverage files to disk. You may also need to set GCOV_PREFIX to some path that's accessible/writable from the app.

First of all, thank everyone for their help.

The llvm_gcov_flush() function was not found, so I used __llvm_gcov_flush().

Now my operation is like this: Still using ndk-r20. Created a very simple android demo, the interface has two buttons: btn_init and btn_flush. Clicking btn_init will set the path:

// The " /data/local/tmp" path is accessible and writable, and my phone is also root.
Setenv("GCOV_PREFIX", "/data/local/tmp", 1); 
setenv("GCOV_PREFIX_STRIP", "100", 1);

Clicking btn_flush will call:

__llvm_gcov_flush();

As far as I know, there is nothing else to set up. However, the .gcda file is still not generated.

Attach all the code:

MainActivity.java

package com.example.konglingzeng.demo;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.system.Os;
import android.view.View;
import android.widget.Toast;
import java.io.File;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        System.loadLibrary("Java2C");
        Toast.makeText(MainActivity.this, "loadLibrary", Toast.LENGTH_LONG).show();

//      new File(android.os.Environment.getExternalStorageDirectory() + "/data_root_directory/gcda/").mkdir();
//        try{
//            Os.setenv("GCOV_PREFIX", android.os.Environment.getExternalStorageDirectory() + "/data_root_directory/gcda/", true);
//            Os.setenv("GCOV_PREFIX_STRIP", "10", true);
//        }
//        catch(Exception e){
//            e.printStackTrace();
//        }

        // Example of a call to a native method
        findViewById(R.id.btn_init).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String input_str = "";
                String res = new Java2CJNI().init(input_str);
                Toast.makeText(MainActivity.this, res, Toast.LENGTH_LONG).show();
            }
        });

        findViewById(R.id.btn_flush).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Java2CJNI().flush();
            }
        });
    }
}

Java2CJNI.java

package com.example.konglingzeng.demo;

public class Java2CJNI {
    public native String init(String str);
    public native void flush();
}

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := Java2C
LOCAL_SRC_FILES := java2C.cc

LOCAL_LDLIBS += -lm -llog -landroid
LOCAL_CFLAGS += -fprofile-arcs -ftest-coverage
LOCAL_CXXFLAGS += -fprofile-arcs -ftest-coverage
#LOCAL_LDFLAGS += -lgcov
LOCAL_LDLIBS += --coverage

#LOCAL_LDFLAGS += -fprofile-dir=/data/local/tmp/

include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_PLATFORM := android-16
APP_ABI := armeabi-v7a
APP_STL := c++_static
APP_OPTIM := debug

com_example_konglingzeng_demo_Java2CJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_konglingzeng_demo_Java2CJNI */

#ifndef _Included_com_example_konglingzeng_demo_Java2CJNI
#define _Included_com_example_konglingzeng_demo_Java2CJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_konglingzeng_demo_Java2CJNI
 * Method:    init
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_konglingzeng_demo_Java2CJNI_init
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_example_konglingzeng_demo_Java2CJNI
 * Method:    flush
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_konglingzeng_demo_Java2CJNI_flush
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

java2C.cc

#include "com_example_konglingzeng_demo_Java2CJNI.h"
#include <stdlib.h>
#include <signal.h>
#include <android/log.h>
#include <setjmp.h>
#include <pthread.h>
#include <dlfcn.h>
#include <stdio.h>

#define TAG "JNITEST"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)

//extern "C" void __gcov_flush();
extern "C" void __llvm_gcov_flush();

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL Java_com_example_konglingzeng_demo_Java2CJNI_init(JNIEnv *env, jobject obj, jstring path)
{
    LOGE("===============================JNI_init");
    setenv("GCOV_PREFIX", "/data/local/tmp", 1);
    //setenv("GCOV_PREFIX", "/storage/emulated/0/data_root_directory/gcda", 1);
    setenv("GCOV_PREFIX_STRIP", "100", 1);

    return env->NewStringUTF("From native !");
}

JNIEXPORT void JNICALL Java_com_example_konglingzeng_demo_Java2CJNI_flush(JNIEnv *env, jobject obj)
{
    LOGE("==============================JNI_flush");
    __llvm_gcov_flush();
    //__gcov_flush();
}

#ifdef __cplusplus
}
#endif
jmgao commented 4 years ago

/data/local/tmp isn't going to be accessible from an app, you'll need to use the app's directory (e.g. /data/data/com.example.konglingzeng.demo), and pull it (either with root adb, or run-as with something like adb shell run-as com.example.konglingzeng.demo tar cf - /data/data/com.example.konglingzeng.demo | tar xfv -).

kong1z commented 4 years ago

/data/local/tmp isn't going to be accessible from an app, you'll need to use the app's directory (e.g. /data/data/com.example.konglingzeng.demo), and pull it (either with root adb, or run-as with something like adb shell run-as com.example.konglingzeng.demo tar cf - /data/data/com.example.konglingzeng.demo | tar xfv -).

Yes! ! ! Generate *.gcda files! ! ! /data/local/tmp isn't going to be accessible from an app !!!

Thank you very much !!!

DanAlbert commented 4 years ago

We don't currently have any docs for coverage, so I'm going to reopen this to track that.